Howto:Initialise a Mega Drive
Before you can start running your game's program on the Mega Drive, you will need to initialize it first. Initializing a Mega Drive is usually the first thing you do in your code, and involves things such as writing to the TMSS register to activate the VDP, clearing RAM, initializing controller ports, and the Z80. Optionally, you can also initialize some VDP registers for use later in your code, although you may choose to do that again every screen mode.
Every Mega Drive game has a header at the first 200h bytes of it's ROM. The TMSS checks a specific address in it for the text "SEGA" and then loads your game's code. Early Model 1 Mega Drives do not have a TMSS - but you still need to have "SEGA" in your header. The header's format is like the one below:
StartOfRom: Vectors: dc.l $FFFE00, EntryPoint, BusError, AddressError dc.l IllegalInstr, ZeroDivide, ChkInstr, TrapvInstr dc.l PrivilegeViol, Trace, Line1010Emu, Line1111Emu dc.l ErrorExcept, ErrorExcept, ErrorExcept, ErrorExcept dc.l ErrorExcept, ErrorExcept, ErrorExcept, ErrorExcept dc.l ErrorExcept, ErrorExcept, ErrorExcept, ErrorExcept dc.l ErrorExcept, ErrorTrap, ErrorTrap, ErrorTrap dc.l HBlank, ErrorTrap, VBlank, ErrorTrap dc.l ErrorTrap, ErrorTrap, ErrorTrap, ErrorTrap dc.l ErrorTrap, ErrorTrap, ErrorTrap, ErrorTrap dc.l ErrorTrap, ErrorTrap, ErrorTrap, ErrorTrap dc.l ErrorTrap, ErrorTrap, ErrorTrap, ErrorTrap dc.l ErrorTrap, ErrorTrap, ErrorTrap, ErrorTrap dc.l ErrorTrap, ErrorTrap, ErrorTrap, ErrorTrap dc.l ErrorTrap, ErrorTrap, ErrorTrap, ErrorTrap dc.l ErrorTrap, ErrorTrap, ErrorTrap, ErrorTrap Console: dc.b 'SEGA MEGA DRIVE ' ; Hardware system ID Date: dc.b '(C)XXXX YEAR.MON' ; Release date Title_Local: dc.b 'YOUR GAME TITLE WILL GO HERE... MEEEEEEEEEEEEEP!' ; Domestic name (Note: has to be 48 bytes long, pad it out with spaces if you do not have 48 characters in your title. Title_Int: dc.b 'YOUR GAME TITLE WILL GO HERE... MEEEEEEEEEEEEEP!' ; International name (Note: has to be 48 bytes long) Serial: dc.b 'GM 10101010-00' ; Serial/version number Checksum: dc.w 0 ; Checksum dc.b 'J ' ; I/O support (Has to be 16 bytes long, so pad it out with spaces.) RomStartLoc: dc.l StartOfRom ; ROM start RomEndLoc: dc.l EndOfRom-1 ; ROM end RamStartLoc: dc.l $FF0000 ; RAM start RamEndLoc: dc.l $FFFFFF ; RAM end SRAMSupport: dc.l $5241F820 ; change to $5241F820 (NOT $5241E020) to create SRAM. 'RA' and $F8 also work. dc.l $200000 ; SRAM start dc.l $200200 ; SRAM end (Gives us $200 ($100 useable) bytes of SRAM) Notes: dc.b ' ' ; Anything can be put in this space, but it has to be 52 bytes. Region: dc.b 'JUE ' ; Region (J=Japan, U=USA, E=Europe)
The first long in the header is what the CPU will initialize the stack pointer to, and the second long tells it where to start to execute your code. The other entries there should be self explanatory for the different exceptions that can occur, and some games write handlers for them. The rest of the longs that point to 'ErrorTrap' are all unused traps and exceptions, so they point to an endless loop. Note that you must have your 'Hardware system ID' start with 'SEGA' for Model 2 and 3 Mega Drives to load your game.
Once you have your header in place, we can move on to the second part.
Set up Hardware
Before you're able to use the Mega Drive to it's fullest, you will need to set up a few things. First of all, many games will start by checking the 'Port C' of the Mega Drive, which seems to indicate if the console is cold-starting (it was powered on) or if it was reset using the reset button. The ports stay in their current state if the main CPU is reset. When this is the case, games will often not do any setup and jump right into clearing the RAM and then starting the main game loop.
EntryPoint: move #$2700,sr ; Disable interrupts. tst.l ($A10008) ; Test port A control. bne.s PortA_Ok ; If so, magically branch. tst.w ($A1000C) ; Test port C control. PortA_Ok: bne.w PortC_Ok move.b ($A10001),d0 ; Get hardware version. andi.b #$F,d0 ; Compare. beq.s SkipSecurity ; If the console has no TMSS, skip the security stuff. move.l #'SEGA',($A14000) ; Make the TMSS happy. SkipSecurity: moveq #0,d0 ; Clear d0. move.l #$C0000000,($C00004) ; Set VDP to CRAM write. move.w #$3F,d7 ; Clear the entire CRAM. VDP_ClrCRAM: move.w d0,($C00000) ; Write 0 to the data port. dbf d7,VDP_ClrCRAM ; Clear the CRAM. lea ($FFFF0000),a0 ; Load start of RAM into a0. move.w #$3FFF,d0 ; Clear $3FFF longwords. moveq #0,d1 ; Clear d1. @clrRamLoop: move.l d1,(a0)+ ; Clear a long of RAM. dbf d0,@clrRamLoop ; Continue clearing RAM if there's anything left. PortC_Ok: bsr.w Init_Z80 ; Initialize the Z80. move #$2300, sr ; Enable interrupts. bra.s MainProgram ; Branch to main program. nop ;------------------------------------- ; Put your program's code below here. ;------------------------------------- MainProgram:
The above code will first check the Port C on the Mega Drive, which will be left in the state it was when the console is reset. Games use this to detect if the game was cold-booted or if the console was reset for some reason. That way, the game will skip clearing RAM and the like if the game was reset. If the game was not reset, it will continue to see if the console has the TMSS, and if it does, write to an area of I/O memory to enable the VDP. It will then go on to clear the VDP's Colour RAM, and then clear main RAM to make sure the game has a clean canvas to work with. Once that's done, it'll initialize the Z80 and you can start your game loop.
Do note that the VDP hasn't been told what to use for registers and the like - you will need to decide what register values you want to use for your game, where you want name tables and the like to be stored, and so on. I'll leave it as an exercise to the reader to figure out how to do that.
Set up the Z80
You might have wondered where
Init_Z80 was defined - it goes somewhat like the following: (and can also be found here.)
Init_Z80: move.w #$100,($A11100) ; Send the Z80 a bus request. move.w #$100,($A11200) ; Reset the Z80. Init_Z80_WaitZ80Loop: btst #0,($A11100) ; Has the Z80 reset? bne.s Init_Z80_WaitZ80Loop ; If not, keep checking. lea (Init_Z80_InitCode),a0 ; Load the start address of the code to a0. lea ($A00000),a1 ; Load the address of start of Z80 RAM to a1. move.w #Init_Z80_InitCode_End-Init_Z80_InitCode-1,d1 ; Load the length of the Z80 code to d1. Init_Z80_LoadProgramLoop: move.b (a0)+,(a1)+ ; Write a byte of Z80 data. dbf d1,Init_Z80_LoadProgramLoop ; If we have bytes left to write, write them. move.w #0,($A11200) ; Disable the Z80 reset. move.w #0,($A11100) ; Give the Z80 the bus back. move.w #$100,($A11200) ; Reset the Z80 again. rts ; Return to sub. ;---------------------------------------------- ; Below is the code that the Z80 will execute. ;---------------------------------------------- Init_Z80_InitCode: dc.w $AF01, $D91F, $1127, $0021, $2600, $F977 dc.w $EDB0, $DDE1, $FDE1, $ED47, $ED4F, $D1E1 dc.w $F108, $D9C1, $D1E1, $F1F9, $F3ED, $5636 dc.w $E9E9 Init_Z80_InitCode_End:
This basically loads the program into the Z80 and has it execute it, mainly to initialize some stuff.
As you might have noticed in the header, there is a reference to "VBlank" and "HBlank" which are the two types of interrupts the VDP will generate for the M68k. The VBlank routine is called whenever the VDP finished drawing the screen (or when the ray inside a CRT hit the bottom right) and this is where you can fire off DMA queues, load new art, run object's code, and more. The HBlank routine is called when the VDP hits a specific horizontal line on-screen, which is specified by a register. This can be used to re-create the water effect you see in the Mega Drive Sonic games. For now, we don't have any real uses for these routines so we will simply tell them to return to wherever the CPU was previously executing:
VBlank: rte ; This is like rts, except it ReTurns from Exception, not from a subroutine. HBlank: rte ; This is like rts, except it ReTurns from Exception, not from a subroutine. ErrorExcept: ErrorTrap: stop #$2700 ; Disable any interrupts. @loop: bra.s @loop ; This just keeps looping forever.
As you can see, there's a
rte making these routines not functional. Do note that you always need an rte at the end, and if you change any CPU registers, be sure to back the old ones up to the stack and restore them before your rte.
You should now have a perfectly initialized Mega Drive. Put your program's main loop at
MainProgram and code away. There's a few interesting things to know about the Mega Drive when programming it, but that is covered in another howto article.