Difference between revisions of "Code header"
(Do'h. Had omitted zero byte from &00,(C).) |
m (Tidied some formatting.) |
||
(8 intermediate revisions by the same user not shown) | |||
Line 14: | Line 14: | ||
==Header== | ==Header== | ||
The general layout of the code header is the following: | The general layout of the code header is the following: | ||
− | + | <div class="mw-widebody"> | |
− | Start+0: JMP Entry | + | Start+0: JMP Entry ; Code entry address or entry point |
− | Start+3: JMP Service | + | Start+3: JMP Service ; Service entry point if a ROM |
− | Start+6: EQUB ROMtype | + | Start+6: EQUB ROMtype ; Code type byte |
− | Start+7: EQUB Copyright-Start | + | Start+7: EQUB Copyright-Start ; Offset to copyright string |
− | Start+8: EQUB | + | Start+8: EQUB Ver:EQUS "Title" ; Version number and Title string |
− | EQUB | + | EQUB &00:EQUS "0.00 (01 Jan 2001)" ; Version string (optional) |
− | Copyright: EQUB | + | Copyright: EQUB &00:EQUS "(C)J.G.Harston" ; Copyright string |
− | Reloc+0: EQUD Start | + | EQUB &00 |
− | Reloc+4: EQUD Offset | + | Reloc+0: EQUD Start ; Load address if ROMtype bit 5 is set |
− | etc: .... | + | Reloc+4: EQUD Offset ; Offsets if specified by the ROMtype |
+ | etc: .... ; Possibly other data | ||
... | ... | ||
− | ALIGN | + | ALIGN ; If the code needs to be aligned |
Entry: | Entry: | ||
+ | </div> | ||
+ | The title, version and copyright strings should be formatted as shown. The | ||
+ | binary version number should be either &0x for version x.yz or &xy for | ||
+ | version x.yz. | ||
+ | |||
+ | Theoretically, the copyright offset can be any value up to &FF, and the | ||
+ | copyright string can be any length, resulting in the relocation addresses | ||
+ | being at any arbitary distance from the start of the file. In practice, the | ||
+ | header should be written so it fits into a maximum of 256 bytes. | ||
===ROM type byte=== | ===ROM type byte=== | ||
The ROM type byte at Start+6 indicates what CPU the code is written for. | The ROM type byte at Start+6 indicates what CPU the code is written for. | ||
− | + | <div class="mw-widebody"> | |
bit 7 - Service entry for ROMs, ignored when loaded | bit 7 - Service entry for ROMs, ignored when loaded | ||
bit 6 - Contains code, if clear will generate an error similar to 'This is not a language' | bit 6 - Contains code, if clear will generate an error similar to 'This is not a language' | ||
Line 37: | Line 47: | ||
bit 4 - Electron key expansion | bit 4 - Electron key expansion | ||
bit 3-0 indicates the CPU type: | bit 3-0 indicates the CPU type: | ||
− | 0 6502 BASIC | + | 0 6502 BASIC 4 - 8 Z80 12 80286 |
− | 1 Turbo6502 | + | 1 Turbo6502 5 - 9 32016 13 ARM |
− | 2 6502 | + | 2 6502 6 - 10 - 14 - |
− | 3 6800/6809/68000 | + | 3 6800/6809/68000 7 PDP11 11 80186 15 - |
− | + | </div> | |
Code in a ROM will normally have a ROM type byte of &E0+cpu, code loaded | Code in a ROM will normally have a ROM type byte of &E0+cpu, code loaded | ||
from a file that is not also a ROM image will normally have a ROM type byte | from a file that is not also a ROM image will normally have a ROM type byte | ||
Line 49: | Line 59: | ||
If the code is run on a CPU that does not match the CPU type an error | If the code is run on a CPU that does not match the CPU type an error | ||
similar to 'This is not Z80 code' is generated. | similar to 'This is not Z80 code' is generated. | ||
+ | |||
+ | Some clients always expect the relocation data to be present even if bit 5 | ||
+ | is clear, so a full header should always be used with the flag byte being | ||
+ | &60+cpu or &E0+cpu. | ||
===Entry point=== | ===Entry point=== | ||
Line 70: | Line 84: | ||
===65x2 - ROM type 0,1,2=== | ===65x2 - ROM type 0,1,2=== | ||
− | Start+0: JMP Entry | + | <div class="mw-widebody"> |
− | Start+3: JMP Service | + | Start+0: JMP Entry ; Code entry point |
− | Start+6: EQUB ROMtype | + | Start+3: JMP Service ; Service entry point |
− | Start+7: EQUB Copyright-Start | + | Start+6: EQUB ROMtype ; flags+&00, flags+&01, flags+&02 |
− | Start+8: EQUB | + | Start+7: EQUB Copyright-Start ; Offset to copyright string |
− | EQUB | + | Start+8: EQUB Ver:EQUS "Title" ; Binary version number and title string |
− | Copyright: EQUB | + | EQUB &00:EQUS "0.00 (01 Jan 2001)" ; Version string |
− | Reloc+0: EQUW Start | + | Copyright: EQUB &00:EQUS "(C)J.G.Harston":EQUB 0 ; Copyright string |
− | Reloc+2: EQUW RelocateTable | + | Reloc+0: EQUW Start ; Load address if ROMtype bit 5 is set |
− | Entry: | + | Reloc+2: EQUW RelocateTable ; Normally 0, only supported by MOS 3.50 |
− | + | ... | |
+ | Entry: ; Code start | ||
+ | </div> | ||
===6809 - ROM type 3=== | ===6809 - ROM type 3=== | ||
− | Start+0: BRA Entry: | + | <div class="mw-widebody"> |
− | or JMP >Entry | + | Start+0: BRA Entry:EQUB 0 ; If position-independent code |
− | Start+3: EQUB &4C:EQUW Service | + | or JMP >Entry ; If not position-independent code |
− | or EQUB &60:EQUW 0 | + | Start+3: EQUB &4C:EQUW Service ; 6502 jump to ROM service handler |
− | Start+6: EQUB flags+&03 | + | or EQUB &60:EQUW 0 ; 6502 RTS if no ROM service handler |
− | Start+7: EQUB Copyright-Start | + | Start+6: EQUB flags+&03 ; ROM type byte |
− | Start+8: EQUB | + | Start+7: EQUB Copyright-Start ; Offset to copyright string |
− | EQUB | + | Start+8: EQUB Ver:EQUS "Title" ; Binary version number and title string |
− | Copyright: EQUS "(C)J.G.Harston":EQUB 0 | + | EQUB &00:EQUS "0.00 (01 Jan 2001)" ; Version string |
− | Reloc+0: EQUD Start | + | Copyright: EQUB &00:EQUS "(C)J.G.Harston":EQUB 0 ; Copyright string |
− | Entry: | + | Reloc+0: EQUD Start ; Load address if ROMtype bit 5 is set |
− | + | ... | |
+ | Entry: ; Code start | ||
+ | </div> | ||
===PDP11 - ROM type 7=== | ===PDP11 - ROM type 7=== | ||
− | Start+0: BR Entry:EQUB 0 | + | <div class="mw-widebody"> |
− | Start+3: EQUB &4C:EQUW Service | + | Start+0: BR Entry:EQUB 0 ; Branch to code |
− | or EQUB &60:EQUW 0 | + | Start+3: EQUB &4C:EQUW Service ; 6502 jump to ROM service handler |
− | Start+6: EQUB flags+&07 | + | or EQUB &60:EQUW 0 ; 6502 RTS if no ROM service handler |
− | Start+7: EQUB Copyright-Start | + | Start+6: EQUB flags+&07 ; ROM type byte |
− | Start+8: EQUB | + | Start+7: EQUB Copyright-Start ; Offset to copyright string |
− | EQUB | + | Start+8: EQUB Ver:EQUS "Title" ; Binary version number and title string |
− | Copyright: EQUB | + | EQUB &00:EQUS "0.00 (01 Jan 2001)" ; Version string |
− | Reloc+0: EQUD Start | + | Copyright: EQUB &00:EQUS "(C)J.G.Harston":EQUB 0 ; Copyright string |
− | Reloc+4: EQUD Entry-Start | + | Reloc+0: EQUD Start ; Load address if ROMtype bit 5 is set |
− | ALIGN | + | Reloc+4: EQUD Entry-Start ; Offset to entry if ROMtype bit 5 set |
− | Entry: | + | ... |
− | + | ALIGN ; Align to 16-bit words | |
+ | Entry: ; Code start | ||
+ | </div> | ||
===Z80 - ROM type 8=== | ===Z80 - ROM type 8=== | ||
− | Start+0: JR Entry: | + | <div class="mw-widebody"> |
− | or JP Entry | + | Start+0: JR Entry:EQUB 0 ; If position-independent code |
− | Start+3: EQUB &4C:EQUW Service | + | or JP Entry ; If not position-independent code |
− | or EQUB &60:EQUW 0 | + | Start+3: EQUB &4C:EQUW Service ; 6502 jump to ROM service handler |
− | Start+6: EQUB flags+&08 | + | or EQUB &60:EQUW 0 ; 6502 RTS if no ROM service handler |
− | Start+7: EQUB Copyright-Start | + | Start+6: EQUB flags+&08 ; ROM type byte |
− | Start+8: EQUB | + | Start+7: EQUB Copyright-Start ; Offset to copyright string |
− | EQUB | + | Start+8: EQUB Ver:EQUS "Title" ; Binary version number and title string |
− | Copyright: EQUB | + | EQUB &00:EQUS "0.00 (01 Jan 2001)" ; Version string |
− | Reloc+0: EQUD Start | + | Copyright: EQUB &00:EQUS "(C)J.G.Harston":EQUB 0 ; Copyright string |
− | Entry: | + | Reloc+0: EQUD Start ; Load address if ROMtype bit 5 is set |
− | + | ... | |
+ | Entry: ; Code start | ||
+ | </div> | ||
===32000 - ROM type 9=== | ===32000 - ROM type 9=== | ||
− | + | Even if the ROM type byte bit 5 is clear the entries at Reloc+0 and Reloc+4 must be present. | |
− | Start+0: EQUB 0:EQUW | + | <div class="mw-widebody"> |
− | Start+3: EQUB &4C:EQUW Service | + | Start+0: EQUB &60:EQUW 0 ; 6502 RTS |
− | or EQUB &60:EQUW 0 | + | or EQUB &4C:EQUW Language65 ; 6502 jump to 6502 language stub |
− | Start+6: EQUB flags+&09 | + | Start+3: EQUB &4C:EQUW Service ; 6502 jump to ROM service handler |
− | Start+7: EQUB Copyright-Start | + | or EQUB &60:EQUW 0 ; 6502 RTS if no ROM service handler |
− | Start+8: EQUB | + | Start+6: EQUB flags+&09 ; ROM type byte |
− | EQUB | + | Start+7: EQUB Copyright-Start ; Offset to copyright string |
− | Copyright: EQUB | + | Start+8: EQUB Ver:EQUS "Title" ; Binary version number and title string |
− | Reloc+0: EQUD Start | + | EQUB &00:EQUS "0.00 (01 Jan 2001)" ; Version string |
− | Reloc+4: EQUD Entry-Start | + | Copyright: EQUB &00:EQUS "(C)J.G.Harston":EQUB 0 ; Copyright string |
− | + | Reloc+0: EQUD Start ; Load address, even if ROMtype bit 5 is not set | |
+ | Reloc+4: EQUD Entry-Start ; Offset to entry, even if ROMtype bit 5 is not set | ||
+ | ... | ||
Entry: | Entry: | ||
− | + | </div> | |
− | === | + | ===80x86 - ROM type 11=== |
− | + | <div class="mw-widebody"> | |
− | Start+0: | + | Start+0: BR Entry:EQUB 0 ; Code is entered here |
− | or | + | or JMP Entry ; |
− | Start+3: EQUB &4C:EQUW Service | + | Start+3: EQUB &4C:EQUW Service ; 6502 jump to ROM service handler |
− | or EQUB &60:EQUW 0 | + | or EQUB &60:EQUW 0 ; 6502 RTS if no ROM service handler |
− | Start+6: EQUB flags+&0B | + | Start+6: EQUB flags+&0B ; ROM type byte |
− | Start+7: EQUB Copyright-Start | + | Start+7: EQUB Copyright-Start ; Offset to copyright string |
− | Start+8: EQUB | + | Start+8: EQUB Ver:EQUS "Title" ; Binary version number and title string |
− | EQUB | + | EQUB &00:EQUS "0.00 (01 Jan 2001)" ; Version string |
− | Copyright: EQUB | + | Copyright: EQUB &00:EQUS "(C)J.G.Harston":EQUB 0 ; Copyright string |
− | Reloc+0: EQUD Start | + | Reloc+0: EQUD Start ; Load address if ROMtype bit 5 is set |
+ | ... | ||
Entry: | Entry: | ||
− | + | </div> | |
===ARM - ROM type 13=== | ===ARM - ROM type 13=== | ||
− | + | See also notes on ARM headers. | |
− | + | <div class="mw-widebody"> | |
− | + | If ROM type byte is &4D or &8D: | |
+ | Start+0: EQUD Entry ; RomFS entry address | ||
+ | Start+4: EQUW 0 | ||
+ | |||
+ | If other ROM type byte values: | ||
+ | Start+0: EQUW (Entry-Start-8)/4:EQUB 0:EQUB &EA ; ARM branch instruction, overlaps Start+3 | ||
+ | Start+4: EQUB &D0:EQUB Service-Start-6 ; 6502 BNE to ROM service handler | ||
+ | or EQUB &60:EQUB 0 ; 6502 RTS if no ROM service handler | ||
+ | or: | ||
+ | Start+0: EQUB &4C:EQUW Entry ; &4C, followed by Entry address | ||
+ | Start+3: EQUB &4C:EQUW Service ; 6502 JMP to ROM service handler | ||
+ | or EQUB &60:EQUW 0 ; 6502 RTS if no ROM service handler | ||
+ | |||
+ | Start+6: EQUB flags+&0D ; ROM type byte | ||
+ | Start+7: EQUB Copyright-Start ; Offset to copyright string | ||
+ | Start+8: EQUB Ver:EQUS "Title" ; Binary version number and title string | ||
+ | EQUB &00:EQUS "0.00 (01 Jan 2001)" ; Version string | ||
+ | Copyright: EQUB &00:EQUS "(C)J.G.Harston":EQUB 0 ; Copyright string | ||
+ | Reloc+0: EQUD Start ; Load address, even if ROMtype bit 5 is not set | ||
+ | Reloc+4: EQUD End-Entry ; Size of code, even if ROMtype bit 5 is not set | ||
+ | ... | ||
+ | ALIGN ; Align to 32-bit words | ||
+ | Entry: | ||
+ | ... | ||
+ | End: | ||
+ | </div> | ||
+ | ==Extracting load/exec address== | ||
+ | File load and execution addresses can be extracted from the code header with | ||
+ | the following code. The execution address is the same as the load address as | ||
+ | the client code calculates the entry point as an offset from the execution | ||
+ | address, which is the first byte of the header. | ||
− | ((( | + | === BASIC code === |
+ | REM Check for file header, using BASIC V or equivalent | ||
+ | in%=OPENIN(A$):IF in% THEN | ||
+ | FOR A%=0 TO 255:IF EOF#in%:buffer%?A%=0 ELSE buffer%?A%=BGET#in% | ||
+ | NEXT A%:CLOSE#in%:in%=0 :REM Read header | ||
+ | IF buffer%!(buffer%?7)=&29432800 THEN | ||
+ | A%=buffer%?6 :REM Code type | ||
+ | load%=&FFFF8000 :REM Sideways ROM | ||
+ | IF A% AND &40:load%=&8000 :REM Language ROM | ||
+ | IF A% AND &20 THEN | ||
+ | A%=buffer%?7:REPEAT A%=A%+1:UNTIL buffer%?A%=0 OR A%>247 | ||
+ | IF A%<248:load%=buffer%!(A%+1) :REM Relocation address | ||
+ | ENDIF | ||
+ | exec%=load% | ||
+ | ENDIF | ||
+ | ENDIF | ||
+ | === C code === | ||
+ | // Check for file header | ||
+ | if(handle=fopen(pathname, "r")) { | ||
+ | memset(buffer, 0, 256); | ||
+ | fread(buffer, 1, 256, handle); | ||
+ | fclose(handle); | ||
+ | off=buffer[7]; | ||
+ | if (buffer[off+0]==0 && buffer[off+1]=='(' && buffer[off+2]=='C' && buffer[off+3]==')') { | ||
+ | type=buffer[6]; | ||
+ | load=0xFFFF8000; // Sideways ROM | ||
+ | if (type & 0x40) load=0x8000; // Language ROM | ||
+ | if (type & 0x20) { // Language relocation address | ||
+ | while (buffer[++off] && off<248); // Step past copyright string | ||
+ | if (++off<249) { | ||
+ | load=(buffer[off+0]&0xFF) | ((buffer[off+1]&0xFF)<<8) | | ||
+ | ((buffer[off+2]&0xFF)<<16) | ((buffer[off+3]&0xFF)<<24); | ||
+ | } | ||
+ | } | ||
+ | exec=load; | ||
+ | } | ||
+ | } | ||
− | + | <!-- | |
− | + | File load and execution addresses can be extracted from the code header with | |
− | the follow pseudo-code | + | the follow pseudo-code. The execution address is the same as the load |
+ | address as the client code calculates the entry point as an offset from the | ||
+ | execution address, which is the first byte of the header. | ||
− | load=unknown | + | load=unknown; exec=load |
− | + | if start[7] does not point to &00,"(C)", then exit // Raw code | |
− | if start | ||
− | |||
− | |||
romtype=start[6] | romtype=start[6] | ||
+ | if (romtype AND 15)=13, then rombyte=rombyte OR &60 // ARM | ||
+ | load=&FFFF8000; exec=load | ||
if rombyte bit 6 is clear, then exit // No code, service ROM | if rombyte bit 6 is clear, then exit // No code, service ROM | ||
− | load=&8000 | + | load=&8000; exec=load |
− | |||
if romtype bit 5 is clear, then exit // No relocation address | if romtype bit 5 is clear, then exit // No relocation address | ||
point to the relocation address after the zero | point to the relocation address after the zero | ||
byte at the end of the copyright string | byte at the end of the copyright string | ||
− | load=reload[0..3] | + | load=reload[0..3]; exec=load |
− | exec= | + | if (romtype AND 15)<>13, then exit // Not ARM |
− | switch ( | + | romtype=start[6] // Get ROM type again |
− | when 7,9: | + | if romtype=&4D or romtype=&8D, then |
− | when 13: | + | exec=start[0..3]; exit // RomFS object |
+ | if romtype bit 5 is set, exit // Relocation address | ||
+ | load=&8000; exec=&8000 // No relocation address | ||
+ | exit | ||
+ | |||
+ | --> | ||
+ | Clients calculate the entry point with the following pseudo-code. | ||
+ | <div class="mw-widebody"> | ||
+ | entry=start | ||
+ | if start[7] does not point to &00,"(C)", then enter code // Raw code | ||
+ | romtype=start[6] | ||
+ | if rombyte bit 6 is clear, then error "This is not code" // No code | ||
+ | if (rombyte AND 15)<>mycpu, then error "This is not MyCPU code" // Wrong CPU | ||
+ | switch (rombyte AND 15) { | ||
+ | when 7,9: // PDP11, 32000 | ||
+ | point to the relocation address after the zero | ||
+ | byte at the end of the copyright string | ||
+ | entry=start+reload[4..7] // Offset to entry point | ||
+ | when 13: // ARM | ||
+ | if start[3]=&EA, then entry=start // ARM branch instruction | ||
+ | else entry=start[1..2] // Embedded address | ||
} | } | ||
− | + | enter code | |
+ | </div> | ||
+ | ==ARM header notes== | ||
+ | The ARM ROM header is complicated by having been implemented differently on | ||
+ | different platforms, the Acorn ARM Evaluation System, the Arthur/RISC OS | ||
+ | RomFS, and the Sprow ARM CoProcessor. (And there is also the RISC OS module | ||
+ | format which most ARM CoPro clients can't *RUN.) | ||
+ | |||
+ | * There is always a relocation address, regardless of the setting of ROMtype bit 5. | ||
+ | * The data at Start+0 may be an ARM branch instruction, a 32-bit entry address or a &4C byte followed by a 16-bit entry address. | ||
+ | * The address at Reloc+0 is the load address of the data after the header in RomFS and is the load address of Start+0 on other systems. | ||
+ | |||
+ | ===ARM Evaluation System=== | ||
+ | The ARM Evaluation System simply jumps to Start+0, so the first word must be | ||
+ | an unconditional branch to the main code. All ARM instructions take four | ||
+ | bytes, so this overlaps into the ROM Service entry. However, an ARM branch | ||
+ | instruction is &EAxxxxxx and &EA is a 6502 NOP instruction, so a null | ||
+ | service entry point can be made with: | ||
+ | |||
+ | Start+0 xx B Entry ; Jump to ARM code | ||
+ | xx | ||
+ | xx | ||
+ | Start+3 EA ; 6502 NOP | ||
+ | 60 EQUB &60 ; 6502 RTS | ||
+ | 00 EQUB &00 | ||
+ | |||
+ | As the ROM service handler is always entered with NE set from testing the | ||
+ | ROM type byte a two-byte BNE instruction can be used to jump to an actual | ||
+ | service handler: | ||
+ | |||
+ | Start+0 xx B Entry ; Jump to ARM code | ||
+ | xx | ||
+ | xx | ||
+ | Start+3 EA ; 6502 NOP | ||
+ | D0 EQUB &D0 ; 6502 BNE | ||
+ | xx EQUB Service-Start-6 | ||
+ | |||
+ | ===Sprow ARM CoPro=== | ||
+ | The Sprow ARM CoPro puts the ARM entry point in the 16-bit address field of | ||
+ | a 6502 JMP instructon, leaving space for a ROM service handler JMP at | ||
+ | Start+3: | ||
+ | |||
+ | Start+0 4C EQUB &4C ; 6502 JMP | ||
+ | xx EQUW Entry | ||
+ | xx | ||
+ | Start+3 4C EQUB &4C | ||
+ | xx EQUW Service | ||
+ | xx | ||
+ | |||
+ | ===ARM RomFS=== | ||
+ | RomFS simply stores a 32-bit entry address at Start+0, overlapping the ROM | ||
+ | service handler at Start+3, so the ROM type byte must have bit 7 clear. The | ||
+ | relocation address at reloc[0...3] and the entry address at start[0..3] are | ||
+ | actually used as load/exec addresses for the data that starts at reloc+8. | ||
+ | |||
+ | ===Identifying ARM header=== | ||
+ | It is not possible to create a single file that will be recognised correctly | ||
+ | by all ARM platforms, but it is possible to examine the header and decide | ||
+ | which ARM platform is is intended for, and consequently, what load and entry | ||
+ | addresses it has. | ||
+ | |||
+ | Start+ | ||
+ | 00 01 02 03 04 05 06 | ||
+ | .. .. .. .. .. .. 0D Raw code | ||
+ | .. .. .. .. .. .. 2D Raw code | ||
+ | ee ee ee ee .. .. 4D RomFS file | ||
+ | dd dd dd EA .. .. 6D ARM Evaluation System | ||
+ | .. ee ee <>EA .. .. 6D Sprow ARM CoPro | ||
+ | |||
+ | ee ee ee ee .. .. 8D RomFS directory | ||
+ | .. .. .. .. .. .. AD Raw code | ||
+ | dd dd dd EA .. .. CD ARM Evaluation System | ||
+ | .. ee ee <>EA .. .. CD Sprow ARM CoPro | ||
+ | dd dd dd EA .. .. ED ARM Evaluation System | ||
+ | .. ee ee <>EA .. .. ED Sprow ARM CoPro | ||
− | [[User:Jgharston|Jgharston]] ([[User talk:Jgharston|talk]]) | + | [[User:Jgharston|Jgharston]] ([[User talk:Jgharston|talk]]) 02:56, 23 January 2017 (UTC) |
Latest revision as of 01:29, 16 February 2023
Files have load and execution addresses that specify where they are loaded to and executed. These specify I/O memory by being &FFxxxxxx and language memory by being <>&FFxxxxxx. The I/O processor is always a 65x2, but the language processor can be one of many CPUs. Files can have a header which specifies what processor the code is written for so if it is run on the wrong processor the client code can generate an error.
The header is the same as the ROM header as ROMs also need to specify what CPU the code is written for and where the code should be copied to in the language memory. The code header also allows files to be loaded from filesystems that don't have load/exec addresses, such as DOS/Windows.
Header
The general layout of the code header is the following:
Start+0: JMP Entry ; Code entry address or entry point Start+3: JMP Service ; Service entry point if a ROM Start+6: EQUB ROMtype ; Code type byte Start+7: EQUB Copyright-Start ; Offset to copyright string Start+8: EQUB Ver:EQUS "Title" ; Version number and Title string EQUB &00:EQUS "0.00 (01 Jan 2001)" ; Version string (optional) Copyright: EQUB &00:EQUS "(C)J.G.Harston" ; Copyright string EQUB &00 Reloc+0: EQUD Start ; Load address if ROMtype bit 5 is set Reloc+4: EQUD Offset ; Offsets if specified by the ROMtype etc: .... ; Possibly other data ... ALIGN ; If the code needs to be aligned Entry:
The title, version and copyright strings should be formatted as shown. The binary version number should be either &0x for version x.yz or &xy for version x.yz.
Theoretically, the copyright offset can be any value up to &FF, and the copyright string can be any length, resulting in the relocation addresses being at any arbitary distance from the start of the file. In practice, the header should be written so it fits into a maximum of 256 bytes.
ROM type byte
The ROM type byte at Start+6 indicates what CPU the code is written for.
bit 7 - Service entry for ROMs, ignored when loaded bit 6 - Contains code, if clear will generate an error similar to 'This is not a language' bit 5 - Contains a relocation address, if clear the code loads to &8000 bit 4 - Electron key expansion bit 3-0 indicates the CPU type: 0 6502 BASIC 4 - 8 Z80 12 80286 1 Turbo6502 5 - 9 32016 13 ARM 2 6502 6 - 10 - 14 - 3 6800/6809/68000 7 PDP11 11 80186 15 -
Code in a ROM will normally have a ROM type byte of &E0+cpu, code loaded from a file that is not also a ROM image will normally have a ROM type byte of &60+cpu. If the ROM type byte is &40+cpu or &C0+cpu there is no relocation address and the code loads to &8000.
If the code is run on a CPU that does not match the CPU type an error similar to 'This is not Z80 code' is generated.
Some clients always expect the relocation data to be present even if bit 5 is clear, so a full header should always be used with the flag byte being &60+cpu or &E0+cpu.
Entry point
The entry point at Start+0 must be an unconditional branch to the code's start point in the machine code for the CPU. If the ROM type byte bit 7 is set then Start+3 contains the ROM service entry point, so the entry point at Start+0 must fit into three bytes. If the ROM type byte bit 7 is clear the code at Start+0 can be up to six bytes long, and some CPUs require this.
If ROMtype bit 7 is set then the entry point at Start+3 must be a 6502 unconditional branch to the ROM's service code, or a 6502 RTS.
The code is entered at the entry point with the registers set to a defined state. This is normally:
Primary register: 0=raw code (no header), 1=code with a header Secondary register: points to command line tail Flags: CC=entered at reset, CS=entered otherwise, eg a *command
Example headers
65x2 - ROM type 0,1,2
Start+0: JMP Entry ; Code entry point Start+3: JMP Service ; Service entry point Start+6: EQUB ROMtype ; flags+&00, flags+&01, flags+&02 Start+7: EQUB Copyright-Start ; Offset to copyright string Start+8: EQUB Ver:EQUS "Title" ; Binary version number and title string EQUB &00:EQUS "0.00 (01 Jan 2001)" ; Version string Copyright: EQUB &00:EQUS "(C)J.G.Harston":EQUB 0 ; Copyright string Reloc+0: EQUW Start ; Load address if ROMtype bit 5 is set Reloc+2: EQUW RelocateTable ; Normally 0, only supported by MOS 3.50 ... Entry: ; Code start
6809 - ROM type 3
Start+0: BRA Entry:EQUB 0 ; If position-independent code or JMP >Entry ; If not position-independent code Start+3: EQUB &4C:EQUW Service ; 6502 jump to ROM service handler or EQUB &60:EQUW 0 ; 6502 RTS if no ROM service handler Start+6: EQUB flags+&03 ; ROM type byte Start+7: EQUB Copyright-Start ; Offset to copyright string Start+8: EQUB Ver:EQUS "Title" ; Binary version number and title string EQUB &00:EQUS "0.00 (01 Jan 2001)" ; Version string Copyright: EQUB &00:EQUS "(C)J.G.Harston":EQUB 0 ; Copyright string Reloc+0: EQUD Start ; Load address if ROMtype bit 5 is set ... Entry: ; Code start
PDP11 - ROM type 7
Start+0: BR Entry:EQUB 0 ; Branch to code Start+3: EQUB &4C:EQUW Service ; 6502 jump to ROM service handler or EQUB &60:EQUW 0 ; 6502 RTS if no ROM service handler Start+6: EQUB flags+&07 ; ROM type byte Start+7: EQUB Copyright-Start ; Offset to copyright string Start+8: EQUB Ver:EQUS "Title" ; Binary version number and title string EQUB &00:EQUS "0.00 (01 Jan 2001)" ; Version string Copyright: EQUB &00:EQUS "(C)J.G.Harston":EQUB 0 ; Copyright string Reloc+0: EQUD Start ; Load address if ROMtype bit 5 is set Reloc+4: EQUD Entry-Start ; Offset to entry if ROMtype bit 5 set ... ALIGN ; Align to 16-bit words Entry: ; Code start
Z80 - ROM type 8
Start+0: JR Entry:EQUB 0 ; If position-independent code or JP Entry ; If not position-independent code Start+3: EQUB &4C:EQUW Service ; 6502 jump to ROM service handler or EQUB &60:EQUW 0 ; 6502 RTS if no ROM service handler Start+6: EQUB flags+&08 ; ROM type byte Start+7: EQUB Copyright-Start ; Offset to copyright string Start+8: EQUB Ver:EQUS "Title" ; Binary version number and title string EQUB &00:EQUS "0.00 (01 Jan 2001)" ; Version string Copyright: EQUB &00:EQUS "(C)J.G.Harston":EQUB 0 ; Copyright string Reloc+0: EQUD Start ; Load address if ROMtype bit 5 is set ... Entry: ; Code start
32000 - ROM type 9
Even if the ROM type byte bit 5 is clear the entries at Reloc+0 and Reloc+4 must be present.
Start+0: EQUB &60:EQUW 0 ; 6502 RTS or EQUB &4C:EQUW Language65 ; 6502 jump to 6502 language stub Start+3: EQUB &4C:EQUW Service ; 6502 jump to ROM service handler or EQUB &60:EQUW 0 ; 6502 RTS if no ROM service handler Start+6: EQUB flags+&09 ; ROM type byte Start+7: EQUB Copyright-Start ; Offset to copyright string Start+8: EQUB Ver:EQUS "Title" ; Binary version number and title string EQUB &00:EQUS "0.00 (01 Jan 2001)" ; Version string Copyright: EQUB &00:EQUS "(C)J.G.Harston":EQUB 0 ; Copyright string Reloc+0: EQUD Start ; Load address, even if ROMtype bit 5 is not set Reloc+4: EQUD Entry-Start ; Offset to entry, even if ROMtype bit 5 is not set ... Entry:
80x86 - ROM type 11
Start+0: BR Entry:EQUB 0 ; Code is entered here or JMP Entry ; Start+3: EQUB &4C:EQUW Service ; 6502 jump to ROM service handler or EQUB &60:EQUW 0 ; 6502 RTS if no ROM service handler Start+6: EQUB flags+&0B ; ROM type byte Start+7: EQUB Copyright-Start ; Offset to copyright string Start+8: EQUB Ver:EQUS "Title" ; Binary version number and title string EQUB &00:EQUS "0.00 (01 Jan 2001)" ; Version string Copyright: EQUB &00:EQUS "(C)J.G.Harston":EQUB 0 ; Copyright string Reloc+0: EQUD Start ; Load address if ROMtype bit 5 is set ... Entry:
ARM - ROM type 13
See also notes on ARM headers.
If ROM type byte is &4D or &8D: Start+0: EQUD Entry ; RomFS entry address Start+4: EQUW 0 If other ROM type byte values: Start+0: EQUW (Entry-Start-8)/4:EQUB 0:EQUB &EA ; ARM branch instruction, overlaps Start+3 Start+4: EQUB &D0:EQUB Service-Start-6 ; 6502 BNE to ROM service handler or EQUB &60:EQUB 0 ; 6502 RTS if no ROM service handler or: Start+0: EQUB &4C:EQUW Entry ; &4C, followed by Entry address Start+3: EQUB &4C:EQUW Service ; 6502 JMP to ROM service handler or EQUB &60:EQUW 0 ; 6502 RTS if no ROM service handler Start+6: EQUB flags+&0D ; ROM type byte Start+7: EQUB Copyright-Start ; Offset to copyright string Start+8: EQUB Ver:EQUS "Title" ; Binary version number and title string EQUB &00:EQUS "0.00 (01 Jan 2001)" ; Version string Copyright: EQUB &00:EQUS "(C)J.G.Harston":EQUB 0 ; Copyright string Reloc+0: EQUD Start ; Load address, even if ROMtype bit 5 is not set Reloc+4: EQUD End-Entry ; Size of code, even if ROMtype bit 5 is not set ... ALIGN ; Align to 32-bit words Entry: ... End:
Extracting load/exec address
File load and execution addresses can be extracted from the code header with the following code. The execution address is the same as the load address as the client code calculates the entry point as an offset from the execution address, which is the first byte of the header.
BASIC code
REM Check for file header, using BASIC V or equivalent in%=OPENIN(A$):IF in% THEN FOR A%=0 TO 255:IF EOF#in%:buffer%?A%=0 ELSE buffer%?A%=BGET#in% NEXT A%:CLOSE#in%:in%=0 :REM Read header IF buffer%!(buffer%?7)=&29432800 THEN A%=buffer%?6 :REM Code type load%=&FFFF8000 :REM Sideways ROM IF A% AND &40:load%=&8000 :REM Language ROM IF A% AND &20 THEN A%=buffer%?7:REPEAT A%=A%+1:UNTIL buffer%?A%=0 OR A%>247 IF A%<248:load%=buffer%!(A%+1) :REM Relocation address ENDIF exec%=load% ENDIF ENDIF
C code
// Check for file header if(handle=fopen(pathname, "r")) { memset(buffer, 0, 256); fread(buffer, 1, 256, handle); fclose(handle); off=buffer[7]; if (buffer[off+0]==0 && buffer[off+1]=='(' && buffer[off+2]=='C' && buffer[off+3]==')') { type=buffer[6]; load=0xFFFF8000; // Sideways ROM if (type & 0x40) load=0x8000; // Language ROM if (type & 0x20) { // Language relocation address while (buffer[++off] && off<248); // Step past copyright string if (++off<249) { load=(buffer[off+0]&0xFF) | ((buffer[off+1]&0xFF)<<8) | ((buffer[off+2]&0xFF)<<16) | ((buffer[off+3]&0xFF)<<24); } } exec=load; } }
Clients calculate the entry point with the following pseudo-code.
entry=start if start[7] does not point to &00,"(C)", then enter code // Raw code romtype=start[6] if rombyte bit 6 is clear, then error "This is not code" // No code if (rombyte AND 15)<>mycpu, then error "This is not MyCPU code" // Wrong CPU switch (rombyte AND 15) { when 7,9: // PDP11, 32000 point to the relocation address after the zero byte at the end of the copyright string entry=start+reload[4..7] // Offset to entry point when 13: // ARM if start[3]=&EA, then entry=start // ARM branch instruction else entry=start[1..2] // Embedded address } enter code
ARM header notes
The ARM ROM header is complicated by having been implemented differently on different platforms, the Acorn ARM Evaluation System, the Arthur/RISC OS RomFS, and the Sprow ARM CoProcessor. (And there is also the RISC OS module format which most ARM CoPro clients can't *RUN.)
- There is always a relocation address, regardless of the setting of ROMtype bit 5.
- The data at Start+0 may be an ARM branch instruction, a 32-bit entry address or a &4C byte followed by a 16-bit entry address.
- The address at Reloc+0 is the load address of the data after the header in RomFS and is the load address of Start+0 on other systems.
ARM Evaluation System
The ARM Evaluation System simply jumps to Start+0, so the first word must be an unconditional branch to the main code. All ARM instructions take four bytes, so this overlaps into the ROM Service entry. However, an ARM branch instruction is &EAxxxxxx and &EA is a 6502 NOP instruction, so a null service entry point can be made with:
Start+0 xx B Entry ; Jump to ARM code xx xx Start+3 EA ; 6502 NOP 60 EQUB &60 ; 6502 RTS 00 EQUB &00
As the ROM service handler is always entered with NE set from testing the ROM type byte a two-byte BNE instruction can be used to jump to an actual service handler:
Start+0 xx B Entry ; Jump to ARM code xx xx Start+3 EA ; 6502 NOP D0 EQUB &D0 ; 6502 BNE xx EQUB Service-Start-6
Sprow ARM CoPro
The Sprow ARM CoPro puts the ARM entry point in the 16-bit address field of a 6502 JMP instructon, leaving space for a ROM service handler JMP at Start+3:
Start+0 4C EQUB &4C ; 6502 JMP xx EQUW Entry xx Start+3 4C EQUB &4C xx EQUW Service xx
ARM RomFS
RomFS simply stores a 32-bit entry address at Start+0, overlapping the ROM service handler at Start+3, so the ROM type byte must have bit 7 clear. The relocation address at reloc[0...3] and the entry address at start[0..3] are actually used as load/exec addresses for the data that starts at reloc+8.
Identifying ARM header
It is not possible to create a single file that will be recognised correctly by all ARM platforms, but it is possible to examine the header and decide which ARM platform is is intended for, and consequently, what load and entry addresses it has.
Start+ 00 01 02 03 04 05 06 .. .. .. .. .. .. 0D Raw code .. .. .. .. .. .. 2D Raw code ee ee ee ee .. .. 4D RomFS file dd dd dd EA .. .. 6D ARM Evaluation System .. ee ee <>EA .. .. 6D Sprow ARM CoPro ee ee ee ee .. .. 8D RomFS directory .. .. .. .. .. .. AD Raw code dd dd dd EA .. .. CD ARM Evaluation System .. ee ee <>EA .. .. CD Sprow ARM CoPro dd dd dd EA .. .. ED ARM Evaluation System .. ee ee <>EA .. .. ED Sprow ARM CoPro