![]() |
![]() |
Sega Megadrive Assembly Programming BasicsCopyright, Lewis Bassett, December 2005
Section 1 - The ROM Header Remember in Chapter 2, we saw how the Sega Megadrive BIOS program checks the ROM that's been inserted into the cartridge slot first thing it's switched on, to make sure it's a proper Megadrive game. It does this by checking the ROM header. The ROM header has two parts, one for the processor, and one for the Megadrive's BIOS chip. The complete header is 512 bytes. I usually write my ROM header into a seperate file. ROM headers take quite a long time to write, so this way, I can re-use my headers, rather than write a new one for every different program. So, before we go any further, open up a new text file and save it as 'header.asm'. This is going to be you're header file, and you can include this at the beginning of every new Megadrive program you write, by using an the 'include' directive. Example: include "header.asm";
The first part of the ROM header is 256 bytes, and contains a long list of addresses which are used by the M68000. Each address in one longword, and points to special type of subroutine, called an exception, which is executed everytime there's an error or outside interruption. The basic idea is, each time there's an error with our code, or each time the processor is interrupted by some external piece of hardware, the M68000 can execute one of our exceptions, rather than simply crash. Each different error or interruption can have a different subroutine, but I only make subroutines for the interruptions that I need. The rest of the errors are all dealt with by one empty subroutine, that I will show you how to make later. This list of addresses is called the Interrupt Vector, and in total contains 64 slots. The first slot contains the address of the stack, which the stack pointer (register a7) will point to. This is almost always an address in the Megadrive's main RAM. The second slot contains the address of the start of our code. This is always a reference to a code label, which we'll put at the start of our program code later. Each of the 62 other slots contain the address of the subroutine associated with a certain error or interruption, this can be a hex address, or a code label. We're going to use code labels. Each of the 64 slots is defined in the source code file like this: dc.l ErrorCodeLabel;Notice that I've declared the code label as a long word (by using 'dc.l', instead of 'dc.w' or 'dc.b'). This is very important, because if each of the 64 slots isn't one long word each, the header won't be aligned properly and the ROM won't run on the Megadrive. Here's the definitions for all 64 errors and interruptions, complete with comments: InterruptVector: dc.l $00FFE000; Slot #1 : The address of the stack dc.l START; Slot #2 : The start of our program dc.l Interrupt; Slot #3 dc.l Interrupt; Slot #4 dc.l Interrupt; Slot #5 dc.l Interrupt; Slot #6 dc.l Interrupt; Slot #7 dc.l Interrupt; Slot #8 dc.l Interrupt; Slot #9 dc.l Interrupt; Slot #10 dc.l Interrupt; Slot #11 dc.l Interrupt; Slot #12 dc.l Interrupt; Slot #13 dc.l Interrupt; Slot #14 dc.l Interrupt; Slot #15 dc.l Interrupt; Slot #16 dc.l Interrupt; Slot #17 dc.l Interrupt; Slot #18 dc.l Interrupt; Slot #19 dc.l Interrupt; Slot #20 dc.l Interrupt; Slot #21 dc.l Interrupt; Slot #22 dc.l Interrupt; Slot #23 dc.l Interrupt; Slot #24 dc.l Interrupt; Slot #25 dc.l Interrupt; Slot #26 dc.l Interrupt; Slot #27 dc.l Interrupt; Slot #28 dc.l HBL; Slot #29 : VDP Horizontal Interrupt dc.l Interrupt; Slot #30 dc.l VBL; Slot #31 : VDP Vertical Interrupt dc.l Interrupt; Slot #32 dc.l Interrupt; Slot #33 dc.l Interrupt; Slot #34 dc.l Interrupt; Slot #35 dc.l Interrupt; Slot #36 dc.l Interrupt; Slot #37 dc.l Interrupt; Slot #38 dc.l Interrupt; Slot #39 dc.l Interrupt; Slot #40 dc.l Interrupt; Slot #41 dc.l Interrupt; Slot #42 dc.l Interrupt; Slot #43 dc.l Interrupt; Slot #44 dc.l Interrupt; Slot #45 dc.l Interrupt; Slot #46 dc.l Interrupt; Slot #47 dc.l Interrupt; Slot #48 dc.l Interrupt; Slot #49 dc.l Interrupt; Slot #50 dc.l Interrupt; Slot #51 dc.l Interrupt; Slot #52 dc.l Interrupt; Slot #53 dc.l Interrupt; Slot #54 dc.l Interrupt; Slot #55 dc.l Interrupt; Slot #56 dc.l Interrupt; Slot #57 dc.l Interrupt; Slot #58 dc.l Interrupt; Slot #59 dc.l Interrupt; Slot #60 dc.l Interrupt; Slot #61 dc.l Interrupt; Slot #62 dc.l Interrupt; Slot #63 dc.l Interrupt; Slot #64Okay, first thing, notice that I've used the same code label for almost every interrupt slot. The code label refers to a subroutine called 'Interrupt'. This will be a simple empty subroutine that will pass control back to processor, to stop it from crashing. We'll write it later. The first slot, which contains the address of the stack, contains the memory address $ffe000. This can be any address in RAM (remember, Megadrive RAM starts at address $ff0000), but I've put it towards the end of the RAM, where it's out of the way. The second slot is used to store the address of the start of our program code, and contains a reference to a code label called 'START'. We'll insert this code label at the beginning of our main program code, just after the header. After the Megadrive's BIOS program has finished checking our ROM, the processor will start executing the instructions at the address in this slot, in this case, the instructions after the label 'START', which we'll insert after the header. All of the rest of the slots contain references to a subroutine called 'Interrupt', all except two. Slot #29 contains a reference to a subroutine called 'VBL', and Slot #31 contains a reference to a subroutine called 'HBL'. These two special subroutines are used by the Video Display Processor, the part of the hardware which controls everything that's sent to the TV screen. I'll explain the function of these two special interrupt subroutines later, in Chapter 6. For the time being, we'll create two empty subroutines later in this chapter, called 'VBL' and 'HBL'. You can copy and paste the Interrupt Vector definitions above, into your 'header.asm' file. They must be in the exact order I've shown them, with NO other code before them.
The second part of the ROM header is also 256 bytes, only this time, it contains information for the Megadrive's BIOS program. The Megadrive is VERY strict about the information contained in this part of the header, and if any of it is wrong, the program won't run. The first thing we define in this part of the header, is the console name. This can either by 'SEGA GENESIS' or 'SEGA MEGA DRIVE' in block capitals, padded out to 16 bytes using spaces (' '), ie, the string must be 16 characters. Example: dc.b "SEGA MEGA DRIVE "; Europe and Japan Console Nameor, if your in America: dc.b "SEGA GENESIS "; America Console NameGenerally, it doesn't matter which of the two you use, just make sure that the definition is 16 BYTES LONG. Remember, that to define strings, the 'dc.b' directive is used, since each seperate character is defined as one byte, making the whole string of characters 16 bytes in total. Again, I can't stress this enough, this string MUST be 16 characters, padded out with black spaces. This warning doesn't just go for the console name, but the whole of the header. The BIOS program expects the information in the header to be lined up exactly perfect, if it isn't 100% perfect, the Megadrive will simply not run the ROM. The next part of the ROM header is the copyright data. This is also exactly 16 bytes long, and contains the copyright sign, a company code, and date, consisting of the month and the year. Example: dc.b "(C)SEGA 2006.JAN";This string must contain the copyright symbol and the dot between the year and the month. Although the company code can be different, I'd recommend that you use the string 'SEGA', since there's only a very limited ammound of company names that can be used. There must be no space in between the copyright sign and the company code. The year can be anything, so long as all four numbers are there. The month can be: 'JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV' or 'DEC'. The whole copyright string MUST be in uppercase characters. The next two strings are the domestic program name, and then the overseas program name. Both strings are 48 characters long (padded with spaces) and can contain anything. Example: dc.b "SAMPLE PROGRAM "; dc.b "SAMPLE PROGRAM ";Remember, the top string is the domestic name, and the bottom name is the overseas name. These strings are declared as bytes, like all strings, with double quotes around them. You must be sure they are each 48 characters, to keep the ROM header aligned properly. The next 14 bytes is the program's serial code. This would have been provided by Sega, along with a licience to develop the program on a Megadrive. Since we're writing unlicienced code, this serial number can be anything, although I prefer to use zeros. Example: dc.b "GM 00000000-00";The first two charcters inform the BIOS program of the type of program being run. This can be 'GM' for a game, 'OS' for an operating system or 'AL' for an education program. A blank space must follow this. The next ten characters are the program's serial number. A hyphen ("-") must follow this. Finally, the last two bytes is the serial number. Providing that the complete string follows the format above, the BIOS program doesn't care which numbers are used. The next word (two bytes) is the checksum. The checksum is a number that is calculated from the value of the every part of the assembled ROM. This can be calulated and inserted into this space using Sega Tool, although it isn't necessary. The BIOS program doesn't check these two bytes, so we can insert any number we like. Example: dc.w $a148;Note that the checksum number is declared as a word, using 'dc.w'. The checksum is usually checked by a subroutine inside the ROM. The idea is, if any of the contents of the ROM is changed (illegally), then the ROM will display and error message and stop running. We don't have to write any code to varify the checksum value, since it's not compulsary. The next 16 bytes is a string, containing a list of all the I/O devices that are supported by our program. Each letter represents a different I/O device, but since we'll only be supporting a joypad, we simply enter just a 'J' (in uppercase). Example: dc.b "J "Note that the rest of the string is padded out with spaces, since the whole string must be 16 bytes. The next two parts of the header are the ROM start and end addresses. Each address is one longword (4 bytes) in length, and can either be an address or a code label. Example: dc.l $00000000; dc.l ROMEnd;The first longword, the ROM start address, is almost always zero. This is the start of the ROM, including the header. The second longword, the end of the ROM, can either be the length of the ROM, in hex padded with zeros, or it can be a code label, in this case ROMEnd, which we'll insert at the end of the program code later. The next two longwords are the start and end address of the general purpose RAM. Remember, this starts at $00ff0000, and finishes as $00ffffff. Example: dc.l $00ff0000; dc.l $00ffffff;These two addresses must be correct, and should not be changed. The next 24 bytes are used for modem support or SRAM support. Modems would have been used for networking programs, like Sega Channel. SRAM is used to save the players progress, and is used in games like Phantasy Star 4 and Sonic 3. Example: dc.b " ";These 24 bytes can be declared as ASCII spaces, like I have above, or by inserting $20 instead. Make sure that the string is exactly 24 bytes. The next 40 bytes are used as a memo, or description of the program. These 40 bytes are ignored by the BIOS, so we can insert anything we like.
dc.b "DEMONSTRATION PROGRAM ";Note: make sure that the memo is padded out to 40 characters, using blank spaces. This is to keep everything in the header correctly aligned so that the BIOS can find it. Finally, 16 bytes is used to specify which countries the program can be released in. This will not affect the BIOS program, as it was used as part of the licience aggreement. However, I still always insert the country codes for USA, Europe and Japan. Example: dc.b "JUE ";Again, use spaces to pad out the string to 16 bytes. The Megadrive's BIOS system is very strict, and if the ROM header isn't 100% correct, it will simply refuse to run the ROM.
That's it! If you've followed this section properly, you should now have a complete ROM header which looks like this: ;******************************************* ;* SEGA Megadrive ROM Header * ;******************************************* InterruptVector: dc.l $00FFE000; Slot #1 : The address of the stack dc.l START; Slot #2 : The start of our program dc.l Interrupt; Slot #3 dc.l Interrupt; Slot #4 dc.l Interrupt; Slot #5 dc.l Interrupt; Slot #6 dc.l Interrupt; Slot #7 dc.l Interrupt; Slot #8 dc.l Interrupt; Slot #9 dc.l Interrupt; Slot #10 dc.l Interrupt; Slot #11 dc.l Interrupt; Slot #12 dc.l Interrupt; Slot #13 dc.l Interrupt; Slot #14 dc.l Interrupt; Slot #15 dc.l Interrupt; Slot #16 dc.l Interrupt; Slot #17 dc.l Interrupt; Slot #18 dc.l Interrupt; Slot #19 dc.l Interrupt; Slot #20 dc.l Interrupt; Slot #21 dc.l Interrupt; Slot #22 dc.l Interrupt; Slot #23 dc.l Interrupt; Slot #24 dc.l Interrupt; Slot #25 dc.l Interrupt; Slot #26 dc.l Interrupt; Slot #27 dc.l Interrupt; Slot #28 dc.l HBL; Slot #29 : VDP Horizontal Interrupt dc.l Interrupt; Slot #30 dc.l VBL; Slot #31 : VDP Vertical Interrupt dc.l Interrupt; Slot #32 dc.l Interrupt; Slot #33 dc.l Interrupt; Slot #34 dc.l Interrupt; Slot #35 dc.l Interrupt; Slot #36 dc.l Interrupt; Slot #37 dc.l Interrupt; Slot #38 dc.l Interrupt; Slot #39 dc.l Interrupt; Slot #40 dc.l Interrupt; Slot #41 dc.l Interrupt; Slot #42 dc.l Interrupt; Slot #43 dc.l Interrupt; Slot #44 dc.l Interrupt; Slot #45 dc.l Interrupt; Slot #46 dc.l Interrupt; Slot #47 dc.l Interrupt; Slot #48 dc.l Interrupt; Slot #49 dc.l Interrupt; Slot #50 dc.l Interrupt; Slot #51 dc.l Interrupt; Slot #52 dc.l Interrupt; Slot #53 dc.l Interrupt; Slot #54 dc.l Interrupt; Slot #55 dc.l Interrupt; Slot #56 dc.l Interrupt; Slot #57 dc.l Interrupt; Slot #58 dc.l Interrupt; Slot #59 dc.l Interrupt; Slot #60 dc.l Interrupt; Slot #61 dc.l Interrupt; Slot #62 dc.l Interrupt; Slot #63 dc.l Interrupt; Slot #64 GameInformation: dc.b "SEGA MEGA DRIVE "; Europe and Japan Console Name dc.b "(C)SEGA 2006.JAN"; Copyright date dc.b "SAMPLE PROGRAM "; Domestic Name dc.b "SAMPLE PROGRAM "; Overseas Name dc.b "GM 00000000-00"; Serial Number dc.w $a148; Checksum dc.b "J " I/O Support dc.l $00000000; ROM Start Address dc.l ROMEnd; ROM End Address dc.l $00ff0000; RAM Start Address dc.l $00ffffff; RAM End Address dc.b " "; No Modem or SRAM Support dc.b "DEMONSTRATION PROGRAM "; Memo dc.b "JUE "; Can be released in Japan, Europe and the US.Now, save the entire header into your new file, named "Header.asm", and move it into the 'lib\' directory in your main 'Megadrive\' folder. This can be included at the start of every new program that you write after the assembler directives, so long as your source file includes the code labels that are used. In the next section, I'll show you how to write the subroutines that are used to handle the interrupts and exeptions. |