Tatara

Chapter 33
A 16 KB ROM cartridge

A cartridge is a program in ROM that the MSX finds and starts on its own, when it is switched on. This chapter builds ROM, the example in A:\TATARA\EXAMPLES\ROM, which makes the image of a 16 KB cartridge: the header the MSX looks for, a routine that prints a line, and the padding that makes the file the right size. It then runs the image in an emulator, and changes it in three ways.

33.1 The example

A cartridge starts before MSX-DOS or MSX-BASIC does: the MSX looks at each slot, finds the cartridge, and calls it while it is still starting. The program can use the BIOS, which is always there, but nothing that MSX-DOS or BASIC provides. ROM sets up a text screen, prints a line, and stays there.

The example has two files, ROM.AS and BUILD.BAT. The result is a file, ROM.ROM, that holds the 16 KB of the cartridge, byte for byte. It is not a program that MSX-DOS can run: it goes into an emulator as a cartridge, or onto a flash cartridge.

33.2 The source

; ROM.AS - a 16 KB cartridge. 
; 
; A cartridge in page 1 begins at 4000h with a sixteen-byte header. 
; The BIOS looks for the letters AB, and calls the INIT address if it 
; is not zero - before there is an operating system of any kind. 
; 
; NOTHING HERE MAY CALL MSX-DOS. There is no MSX-DOS yet. The screen 
; and the printing are the BIOS, and the code never returns, because 
; there is nothing to return to. 
; 
; THE LAST TWO LINES ARE WHAT MAKES THE FILE 16 KB. TANREN writes the 
; span from the lowest byte of content to the highest, and a gap 
; inside that span comes out as zeros. One byte at 7FFFh therefore 
; makes the file exactly 16384 bytes, which is what a cartridge has 
; to be. A DS would not do it: the space a DS reserves after the last 
; byte of content is deliberately not in the file. 
 
                include bios.inc        ; INITXT, CHPUT, and the rest 
                include ascii.inc       ; CHR_CR and CHR_LF 
 
                aseg 
                org     4000h 
 
                db      "AB"            ; a cartridge, says the BIOS 
                dw      init            ; INIT: called at once 
                dw      0               ; STATEMENT: no CALL statements 
                dw      0               ; DEVICE: no device 
                dw      0               ; TEXT: no BASIC program in here 
                dw      0,0,0           ; reserved, and zero 
 
init:           call    INITXT          ; a text screen, from nothing 
                ld      hl,msg 
ini.lp:         ld      a,(hl) 
                or      a 
                jr      z,ini.end 
                push    hl 
                call    CHPUT 
                pop     hl 
                inc     hl 
                jr      ini.lp 
ini.end:        jr      ini.end         ; a cartridge has nowhere to go 
 
msg:            db      "Assembled by Tatara. Linked by TANREN.",CHR_CR,CHR_LF,0 
 
                org     7fffh           ; the last byte of the 16 KB... 
                db      0               ;   ...so that the file is all of it 
 
                end
 

33.2.1 The header

A 16 KB cartridge sits at 4000h to 7FFFh, and its first sixteen bytes are a header that tells the MSX what the cartridge holds (figure 33.1):

PIC

Figure 33.1: The header of ROM.ROM, at 4000h, and the value of each field.

The MSX looks for the header at 4000h and at 8000h (section 26.6). ASEG and ORG 4000h put it at 4000h, with nothing for TANREN to move.

33.2.2 INIT

init calls INITXT, the BIOS routine that sets up the text screen, prints the message with CHPUT, and then jumps to itself for ever: ini.end: jr ini.end. The MSX stays on the screen the cartridge left it on. This is how a game, for example, takes the machine over.

33.2.3 The last two lines

A cartridge image has to hold all its 16 KB. TANREN writes the memory from the first byte with content to the last (section 24.1.1), so the program alone would make a file of 77 bytes. org 7fffh and db 0 put a byte at the last address, and everything between the message and 7FFFh is written as zeros.

33.3 Building it

rem BUILD.BAT - EXAMPLE: ROM 
rem 
rem A 16 KB cartridge image. No MSX-DOS anywhere in it: the code runs 
rem from a cartridge slot before there is an operating system, so it 
rem calls the BIOS and never returns. 
rem 
rem   ASEG and ORG 4000h   page 1, where a 16 KB cartridge lives 
rem   no /B                a ROM has no header of any kind 
rem   ORG 7FFFH and DB 0   what makes the file exactly 16384 bytes 
rem 
rem THIS IS THE ONE EXAMPLE A SUCCESSFUL BUILD DOES NOT PROVE. Load 
rem ROM.ROM as a cartridge in an emulator, or write it to a flash 
rem cartridge. It is not a program whose name you can type. 
 
rem The headers are in A:\TATARA\INCLUDE and are not copied here. 
rem TATARA is rule 3 of the include search - see the DIRS example. 
rem CHANGE THE PATH BELOW if you put the tree somewhere else. 
 
set TATARA=a:\tatara\include 
 
echo === Assembling 
tatara /q rom.as rom.tro 
 
echo === Linking, with no header of any kind 
tanren /q /o:rom.rom rom.tro 
 
echo === Done. ROM.ROM should be exactly 16384 bytes. 
 
set TATARA=
 

The link has no /B: the file is the cartridge’s own bytes, with no BLOAD header in front of them. Its only header is the one in the source.

A:\TATARA\EXAMPLES\ROM>build 
=== Assembling 
=== Linking, with no header of any kind 
=== Done. ROM.ROM should be exactly 16384 bytes.
 

Without /Q, TANREN’s summary confirms the size:

A:\TATARA\EXAMPLES\ROM>tanren /o:rom.rom rom.tro 
... 
1 modules, 6 records, ends at EOF. 
Wrote ROM.ROM, 4000-7FFF (16384 bytes).
 

The file starts with 41 42 10 40, AB and the address of init, 4010h, low byte first, followed by the ten zero bytes of the other fields. init begins at 4010h with CD 6C 00, call INITXT.

33.4 Running it

Insert ROM.ROM as a cartridge in an emulator and reset the MSX. In openMSX, for example, the console command carta inserts a ROM image in the first cartridge slot, and reset restarts the machine. The screen shows the message on a text screen, and nothing else:

Assembled by Tatara. Linked by TANREN.
 

The MSX found AB at 4000h, called init, and never came back from it. Neither MSX-DOS nor BASIC starts.

33.5 Things to try

The files below are not part of the example: make them in its directory, with TATARA set as in BUILD.BAT, and link each one as ROM.AS is linked.

33.5.1 Return from INIT

ROMRET.AS is ROM.AS with ret in place of the endless jump:

ini.end:        ret                     ; and the MSX goes on starting
 

The message appears for a moment, and then the MSX goes on starting as it would without the cartridge; on an MSX with a disk drive, MSX-DOS starts. The comment at the top of ROM.AS says there is nothing to return to, but the MSX does wait for INIT to return. A cartridge that only adds something to the system, and leaves the rest of the start to it, sets up what it needs and returns.

33.5.2 Spoil the ID

ROMBAD.AS is ROM.AS with "AC" in place of "AB":

                db      "AC"            ; not a cartridge ID
 

It links into a 16 KB file like the others, but the MSX does not take it for a cartridge. Nothing of it is seen: the MSX starts MSX-DOS as if the slot were empty. When a cartridge does nothing at all, check the first two bytes of the file.

33.5.3 Let TANREN place the cartridge

A cartridge does not have to use ASEG. ROMREL.AS is the same cartridge in the default code segment, placed at 4000h by /P: (section 24.3):

; ROMREL.AS - the cartridge as a relocatable module, placed by /P:4000. 
                include bios.inc 
                include ascii.inc 
                cseg 
header:         db      "AB" 
                dw      init 
                dw      0,0,0 
                dw      0,0,0 
init:           call    INITXT 
                ld      hl,msg 
ini.lp:         ld      a,(hl) 
                or      a 
                jr      z,ini.end 
                push    hl 
                call    CHPUT 
                pop     hl 
                inc     hl 
                jr      ini.lp 
ini.end:        jr      ini.end 
msg:            db      "Placed by TANREN /P:4000.",CHR_CR,CHR_LF,0 
                ds      3FFFh-($-header) 
                db      0 
                end
 

ORG cannot pad a relocatable segment to a fixed address, so the padding is worked out from the size: $-header is the number of bytes so far, and the DS reserves the rest of the 16 KB but one. The DB after it fills the last byte, so that the DS is inside the file and not after its end.

A:\TATARA\EXAMPLES\ROM>tatara /q romrel.as romrel.tro 
A:\TATARA\EXAMPLES\ROM>tanren /p:4000 /o:romrel.rom romrel.tro 
... 
1 modules, 7 records, ends at EOF. 
Wrote ROMREL.ROM, 4000-7FFF (16384 bytes).
 

As a cartridge, it prints its own line and stays there:

Placed by TANREN /P:4000.
 

Written this way, the code of a cartridge can be spread over several modules, as any other program, and TANREN places them all.