Sega Megadrive – 1: Getting Started

My favourite videogames console of all time – the Sega Megadrive. I’ve been pretty excited about getting started on this machine for many years now, and has been the catalyst which finally kicked me into learning some assembly language.

Now, I’ve jumped the gun a bit, since I was originally planning to work on these platforms in chronological order, which means the Nintendo Entertainment System is going to wait in the queue for a while (don’t be fooled, the Sega Master System II was released AFTER the Megadrive, because Sega are nuts like that). I also don’t yet have any development hardware (I’m currently in negotiations with some sellers, though), so I’ll be starting out with a PC emulator with debugging features. The Sonic disassembly packages over at Sonic Retro contain a modified (fixed for Windows 7) version of SN Systems’ 68000 assembler, which was a low cost alternative to Sega’s tools at the time, and used by many Megadrive developers.

The point is, I’m just too excited to leave this console alone, and if anything will kickstart my motivation for this project with a flying leap it will be the Megadrive.

The Sega Megadrive technical specifications

A quick and naive list of the console’s basics, but it’s all I need to know to get started:

  • CPU: Motorola 68000 at 7.61 mhz
  • Slave CPU: Zilog z80
  • Main memory: 64kb
  • Video: Yamaha YM7101 VDP (Video Display Processor)
  • Video memory: 64kb
  • Audio: Yamaha YM2612 FM chip, Texas Instrumentsย SN76489 chip
  • Game media: Cartridge
  • Programming language: 68k Assembler language
  • Known development hardware: Official Sega Genesis dev unit, Cross Products MegaCD unit

The Tools

As mentioned, I’ve managed to get hold of the SN Systems ASM68K assembler, a command line tool for MS-DOS. Since there was no official IDE or text editor included, nor can I find any clues as to what was commonly used at the time, I’ll be using Microsoft Visual Studio, simply because I’m familiar with its keyboard shortcuts.

Until I can get hold of some hardware, I’ll be making use of a PC emulator which has some debugging features. After some searching around, it seems the MAME emulator MESS does a good job, Gens with the KMod plugin is capable of debugging, and I’ve also had Regen recommended to me on the ASSEMblergames.com forums. I’m inclined to start with MESS since it uses the same debugging shortcut keys as Visual Studio.

Testing the Assembler

Since documentation seems scarce, I’ve used the Sonic the Hedgehog disassemblies from Sonic Retro as a guide. The package contains a batch file used to build the Sonic source, and the assembler bit simply boils down to:

ASM68K.EXE source.asm,destination.bin

Let’s test something out:

Loop:
   move.l #0xF, d0 ; Move 15 into register d0
   move.l d0, d1   ; Move contents of register d0 into d1
   jmp Loop        ; Jump back up to 'Loop'

…and that assembles just fine:

SN 68k version 2.53
Assembly completed.
0 error(s) from 4 lines in 0.1 seconds

I won’t pretend that I just came up with that assembly snippet like it was natural, it’s been a while since I last touched some 68k assembly (on the Atari STe) and it was the result of an hour or so of trawling through documentation and example code to refresh my memory!

The Megadrive ROM header

Unfortunately, it’s not as simple as loading up the generated ROM into an emulator and hitting Debug. A Megadrive ROM needs a header, which contains some meta info about the ROM, and a block of CPU vectors used to initialise the 68000 before the code gets executed. The header takes up 512 bytes at the very top of the ROM, and looks a little something like this:

	; ******************************************************************
	; Sega Megadrive ROM header
	; ******************************************************************
	dc.l   0x00FFE000      ; Initial stack pointer value
	dc.l   EntryPoint      ; Start of program
	dc.l   Exception       ; Bus error
	dc.l   Exception       ; Address error
	dc.l   Exception       ; Illegal instruction
	dc.l   Exception       ; Division by zero
	dc.l   Exception       ; CHK exception
	dc.l   Exception       ; TRAPV exception
	dc.l   Exception       ; Privilege violation
	dc.l   Exception       ; TRACE exception
	dc.l   Exception       ; Line-A emulator
	dc.l   Exception       ; Line-F emulator
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Spurious exception
	dc.l   Exception       ; IRQ level 1
	dc.l   Exception       ; IRQ level 2
	dc.l   Exception       ; IRQ level 3
	dc.l   HBlankInterrupt ; IRQ level 4 (horizontal retrace interrupt)
	dc.l   Exception       ; IRQ level 5
	dc.l   VBlankInterrupt ; IRQ level 6 (vertical retrace interrupt)
	dc.l   Exception       ; IRQ level 7
	dc.l   Exception       ; TRAP #00 exception
	dc.l   Exception       ; TRAP #01 exception
	dc.l   Exception       ; TRAP #02 exception
	dc.l   Exception       ; TRAP #03 exception
	dc.l   Exception       ; TRAP #04 exception
	dc.l   Exception       ; TRAP #05 exception
	dc.l   Exception       ; TRAP #06 exception
	dc.l   Exception       ; TRAP #07 exception
	dc.l   Exception       ; TRAP #08 exception
	dc.l   Exception       ; TRAP #09 exception
	dc.l   Exception       ; TRAP #10 exception
	dc.l   Exception       ; TRAP #11 exception
	dc.l   Exception       ; TRAP #12 exception
	dc.l   Exception       ; TRAP #13 exception
	dc.l   Exception       ; TRAP #14 exception
	dc.l   Exception       ; TRAP #15 exception
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)
	dc.l   Exception       ; Unused (reserved)

	dc.b "SEGA GENESIS    "									; Console name
	dc.b "(C)SEGA 1992.SEP"									; Copyrght holder and release date
	dc.b "YOUR GAME HERE                                  "	; Domestic name
	dc.b "YOUR GAME HERE                                  "	; International name
	dc.b "GM XXXXXXXX-XX"									; Version number
	dc.w 0x0000												; Checksum
	dc.b "J               "									; I/O support
	dc.l 0x00000000											; Start address of ROM
	dc.l __end												; End address of ROM
	dc.l 0x00FF0000											; Start address of RAM
	dc.l 0x00FFFFFF											; End address of RAM
	dc.l 0x00000000											; SRAM enabled
	dc.l 0x00000000											; Unused
	dc.l 0x00000000											; Start address of SRAM
	dc.l 0x00000000											; End address of SRAM
	dc.l 0x00000000											; Unused
	dc.l 0x00000000											; Unused
	dc.b "                                        "			; Notes (unused)
	dc.b "JUE             "									; Country codes

Note that the assembler requires code and data to be tabbed one to the right, I’ll look into why this is necessary at a later date. Labels, however seem happy with no tabs.

The top section is a block of CPU vectors, read in when the system boots, and are used to initialise various registers and interrupt addresses. The first longword is the value of the stack pointer register when the system boots, although the rest of the registers must be initialised manually so I’m confused as to why this one must be explicitly set. The EntryPoint is the address of the first line of code that gets run, and the majority of the rest point to an exception routine to catch errors. Eventually I plan to write a proper exception handler for each type of problem, and print to screen some information which would help me diagnose the issue.

The HBlankInterrupt and VBlankInterrupt are routines that get called when the electron beam in the TV reaches the right hand side of the screen, and when the beam hits the bottom right before switching off and moving back to the top left. I guess modern LCD and plasma ย TVs don’t have this concept, but from the examples I’ve seen the timing for these interrupts being called is clock-accurate, so they’re perfect for implementing timers.

The second block is some information about the cartridge, hopefully the comments are self explanatory. The ROM/SRAM start and end addresses make sense to me since a cartridge and its savegame space (if any) can be of variable size, but I’ve yet to discover why the RAM start and end addresses need explicitly defining. The checksum is not read by the boot code itself and nothing is done with it, it’s only there for the programmer to implement a check if they wish.

All of the addresses can just be specified in hex, but the assembler allows for labels which makes things a great deal easier. EntryPoint, Exception, __end, HBlankInterrupt and VBlankInterrupt will need defining:

EntryPoint:
   Loop:
   move.l #0xF, d0 ; Move 15 into register d0
   move.l d0, d1   ; Move contents of register d0 into d1
   jmp Loop        ; Jump back up to 'Loop'

HBlankInterrupt:
VBlankInterrupt:
   rte   ; Return from Exception

Exception:
   rte   ; Return from Exception

__end    ; Very last line, end of ROM address

EntryPoint just loops around the little snippet I used to test out the assembler above. Both H/VBlankInterrupts and the Exception handler do nothing and return for now, I’ll experiment with those later. __end contains no code, it’s just a marker for the address of the last byte of the ROM. I’ve prefixed the label with underscores, simply to indicate that it’s not a subroutine and shouldn’t be called explicitly.
Ok, it should be ready to build and run!

Debugging the ROM

The ROM assembles with the ASM68k.EXE line demonstrated earlier. My chosen emulator, MESS, needs to be configured to enable the debugging features. After running MESS once, a mess.ini file is generated alongside the .exe, which contains a debug flag which can be set to 1. Now the ROM can be run using:

mess64.exe genesis -cart test.bin

MESS fires up, loads the ROM, and displays a debugging window. Unfortunately, I ran into a problem: the disassembly window shows garbage. The opcodes are mostly ‘ori’ and ‘illegal’, and I couldn’t make head or tail of my code:

After some digging around and tearing my hair out, the guys at ASSEMblergames.com pointed out that the first 15 bytes of my ROM didn’t belong there (I’m assuming the assembler added some sort of meta data to the start of the binary, perhaps for the SN debugger), and would need removing before it would work. After deleting those bytes using a hex editor (or assembling with the /p option), the ROM seems to work:

Much better, the opcodes are recognisable now. Time to test it out – MESS uses the same keyboard shortcuts for debugging as Visual Studio:

  • F9 – Set/unset breakpoint
  • F10 – Step over
  • F11 – Step into
  • SHIFT + F11 – Step out

So, after a single step (F10) the program counter moves straight to the address specified as the entry point in the header and executes it, and the value 0xF is moved into register r0. After a second step the contents of r0 (still 0xF) are moved into register r1, and after a third step the program counter is jumped back up to the first line again:

It’s not exactly Crysis, but it demonstrates that everything is in the right place and ready for the next part – initialising the Megadrive.

Matt

References

Advertisements

38 thoughts on “Sega Megadrive – 1: Getting Started

  1. I’m new to this entire world of stuff and while I’m imagine there are far easier things to learn then programming with the MD, I’m dead set on this. It’s been a goal of mine for some time.

    Your articles are a great help so thanks! ๐Ÿ™‚

    • It was the same for me learning 68k, I dabbled with the Atari ST for a short while which has the luxury of an IDE and debugger on the machine, and plenty of decent tutorials, but I couldn’t wait to make a start on the Megadrive. Of all the assembly languages I’ve looked at though, 68k seems very slick. I’m glad I didn’t put this off any longer, it’s been in my mind for many years.

      Are you hoping to make a full game? It would be awesome to see some more new Megadrive games, the last one was… Beggar Prince? I think.

  2. I’m not even going to think of making an entire game at the moment, but maybe sometime in the next 50 years haha. ๐Ÿ™‚

    I think the most frustrating thing for me is knowing what I want to do and seeing it in my mind but not physically being able to do it at the moment. Guess that’s what notebooks are for.

    That however is very motivating for me. I’ve got a ton of free time so I might as well put it into something I want to do.

  3. Hey, you should change the rts in the interrupts to rte, so as to not confuse anyone new
    to assembly programming. ๐Ÿ™‚

  4. Not really, I just assumed interrupts used something other than RTS since it is like that for the NES and SNES, so I just looked if there was a similar opcode. Strange that it is new to you tho, I downloaded your HScrolling demo, and in the interrupts file, you return from both HBlank and VBlank with RTE, there is no return command from other exceptions tho.

    • Ok, here is the difference between rts and rte.
      RTS: (sp)+ > pc
      RTE: (sp)+ > sr; (sp)+ > pc

      I don’t know what sr is, but I guess sp would be the stack pointer and pc the program counter.

      • Ahh yes it’s all coming back to me now. SR is the status register, it’s restoring it since most opcodes will alter it. I changed it in the HScrolling demo for this very reason, and completely forgot to mention it in my article!

  5. I feel dumb but I’m having trouble repeating what you’ve done. I’ve downloaded asm68k, and the first snipet of code, (the Loop) will assemble but unlike yours, it says, its more than 4 lines, which makes no sense. Then if I copy and paste your rom header and following, it complains about dc.l not being a recognized op-code. Is there something specific I’m missing or not understanding about setup of asm68k?

  6. I am aware dc.l isn’t a opcode thus why I’m so confused as asm68k’s behavior. As far as indents/spacing. that’s the thing, I’ve tried typing it in, and I’ve tried copy and pasting, and it behaves the same way. I’ve messed with the indentation quite a bit. I’m pretty stumped. Any possibility you have your source file so i can compare what I have?

  7. I’m starting to wonder if there’s some reason windows 8/10 development build isn’t playing nice with it. I’ve tried running in compatibility mode and that doesn’t seem to get me anywhere. I’m about to bust out my windows XP laptop and see if it behaves the same way there. I’m using Notepad++ with the asm68k template installed for writing source code.

    • Hi!

      It looks like you haven’t defined EntryPoint, Exception, HBlankInterrupt, VBlankInterrupt or __end. A header alone isn’t enough.

      As for the Pong example, are you assembling using the /p option? Without it, there will be 16 bytes of debug info at the start of the ROM that emulators cannot read.

      • You need to add subroutines for the missing entries, and mark the very end of your ROM with __end (make sure the tabbing is correct, too):


        EntryPoint:
        ; Main entry point into program, your code goes here (just loop for now)
        jmp EntryPoint

        HBlankInterrupt:
        ; Called when a HBLANK happens
        rte

        VBlankInterrupt:
        ; Called when a VBLANK happens
        rts

        Exception:
        ; Called when something goes wrong
        rte

        ; End of ROM
        __end:

      • I’m getting another problem now -_-

        SN 68k version 2.53

        C:\USERS\FRAN VALEN TOMY\DOCUMENTS\MEGADRIVE\DEV\TEST.ASM(102) : Error : Label ‘entrypoint’ multiply defined
        entrypoint:
        C:\USERS\FRAN VALEN TOMY\DOCUMENTS\MEGADRIVE\DEV\TEST.ASM(105) : Error : Label ‘hblankinterrupt’ multiply defined
        hblankinterrupt:
        C:\USERS\FRAN VALEN TOMY\DOCUMENTS\MEGADRIVE\DEV\TEST.ASM(108) : Error : Label ‘vblankinterrupt’ multiply defined
        vblankinterrupt:
        C:\USERS\FRAN VALEN TOMY\DOCUMENTS\MEGADRIVE\DEV\TEST.ASM(111) : Error : Label ‘exception’ multiply defined
        exception:
        C:\USERS\FRAN VALEN TOMY\DOCUMENTS\MEGADRIVE\DEV\TEST.ASM(115) : Error : Label ‘__end’ multiply defined
        __end:
        Errors during pass 1 – pass 2 aborted
        Assembly completed.
        5 error(s) from 153 lines in 0.0 seconds

  8. This is awesome. I’m currently learning C64 6510 asm and my goal is to make full commercial games for older consoles. I’ll move onto 68k and Mega Drive once I’ve had a bit of success on the C64. Haha. So much more fun than iOS dev…

  9. Hmmm my current tool chain is Easy68K to write my code, compile and debug it (assuming I only need to test the 68K side of things) and then GENS KMod to debug on the Megadrive. However looking into it the debugger in GENS is, by Kaneda’s own admission, ‘not very accurate’ so looking at your setup here I think it is time I updated my work flow.

    MESS, with it step by step debugging looks like a superior option in this case although for now at least I think I’ll stick with Easy68K for the coding.

    • I’d be interested to hear how you get on. One of the biggest frustrations with Mega Drive homebrew at the moment is debugging without source, or at least comments from source. It seems to be possible with a GCC based setup and some emulator plugins but other than that options are fairly limited. I’m lucky since I’ve managed to acquire a complete working development unit, which I’m currently using to develop Tanglewood, but that’s not a viable option for most. I only know of three working kits worldwide.

      Keep an eye out for UMDK – a USB-to-cartridge debugger that’s in its infancy but is starting to become available in small quantities. Again, its geared towards GCC environments, so it has limited use to me at the moment.

      The most accurate emulator I’ve seen so far is Exodus, which aims to be a cycle accurate representation of real hardware. It’s very slow, but great for debugging.

  10. Thanks a bunch for making this. I just worked through it all and I’m about to move on to part 2.

    I just wanted to let you know that you’re missing a colon after __end in your sample code.

    Cheers!

  11. This I awesome ๐Ÿ˜€ I’m currently coding my SMD game in C but want to make the jump to ASM. I have the toolchain set up to run from Dropbox so I can use it on all machines and it seems way more robust when compiling than the C setup. Hopefully I can get my ASM skills close to my C skills (which aren’t amazing) and get this game finished this year ๐Ÿ˜€

    Thanks heaps!!!!

  12. Why am I getting
    ” symbol ‘nullinterrupt’ not defined ” when compiling (with /p)

    My source is copied straight from yours, nothing seems to be different. I have even stopped using my header (which was still copy and paste from this page) and taken your pong one…

    include ‘header.asm’

    EntryPoint:
    Loop:
    move.l #0xF, d0 ; Move 15 into register d0
    move.l d0, d1 ; Move contents of register d0 into d1
    jmp Loop ; Jump back up to ‘Loop’

    HBlankInterrupt:
    VBlankInterrupt:
    rte ; Return from Exception

    Exception:
    rte ; Return from Exception

    __end ; Very last line, end of ROM address

    I was however able to compile pong.

    • Never mind, it was something stupid you mentioned.
      I don’t know why your header file is different for Pong not sure about how all this works yet, but it was causing the initial error. Went back to the header off this page, pasted and tabbed it all across one indent and it compiled. Onto the 2nd part of your venture now ๐Ÿ™‚

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s