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
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.
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.
I believe Pier Solar was created some time after the Beggar Prince, but I am not 100% positive. I have played through the first hour or so of Pier Solar and it is great. Highly recommended.
I need to get myself a copy of that. Do you know if they’re going to reprint?
Hey, you should change the rts in the interrupts to rte, so as to not confuse anyone new
to assembly programming. 🙂
This one’s new to me. Just looked it up – “ReTurn from Exception”. Makes sense, but any idea why it’s different from RTS?
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!
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?
Hi! dc.l isn’t an opcode, it’s a storage identifier, which probably means you’ve indented the header with a tab or some spaces. Try removing the space.
Also, if the four lines assemble to a jumble of mess, make sure to assemble with the /p option
I know this is quite a while later, but to get it to work I actually had to tab in all the lines in the header file. Just copying and pasting the above example won’t actually put in the tabs for you.
yes, it´s been several years now. In case anyone is starting these tutorials now just like me, some tips:
ASM68K assembler that works in 64-bit Windows 10 can be found here:
https://info.sonicretro.org/File:ASM68k.7z
Oddly enough, to avoid the “Opcode not recognized” error is necessary to insert one Tab right before all opcodes and directives .
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?
I also am already using /p
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.
I’ve been using ASM68K on Windows 8 without issues so far. Unfortunately the original files from this post died along with FileDen.com when it changed hands, but here’s a complete example: http://www.mediafire.com/download/h4qmqf65eoki7yb/MD_PongTest.zip
It’s an accumulation of everything in this blog demonstrated in Pong form. Take a look at FRAMEWK\INIT.ASM and PONG/HEADR.ASM
When trying to compile the rom header, it gives me symbol not defined error on the compiler.
Also, compiling the pong example it gives a corrupter rom that does not boot.
http://devster.proboards.com/thread/1059/error-symbol-defined-asm68k?page=1&scrollTo=10430
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.
I fixed the pong problem (i fogot the /p command xD), but i dont understad how to fix the symbol no defined in ROM header.
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
EDIT: fixed, the problem is that had put before the labels at the start of the code and i forgot to delete them xD
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…
Awesome! I have a C64 Laser Genius kit somewhere (https://c2.staticflickr.com/8/7318/10768272014_83a5320136_z.jpg) but never got the chance to try something out. Maybe one day!
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.
Hey Matt, a little over 2 years since my original comment here and it is great to see how much you have progressed in your own 68K deveopment as well as your game. I happened upon a Computerfile video on YouTube and saw you explaining Megadrive programming to the world at large and it got me wondering if your blog, and the original posts, were still up.
How you feeling about things? Can you cast your memory back to when you was writing this and could you have seen yourself then where you are now?
Sadly my development journey hasn’t moved as far or as fast as yours, but it is still ongoing (I like to try and make myself believe that anyway!). I’m 4% through decompiling Toejam and Earl, my favourite MD game. It’s taken a back seat to other projects though, but reading through these posts and seeing your progress really gives me the itch to pick it up again…
It’s been a whirlwind of an adventure, very exciting but very stressful at the same time, I had absolutely no idea it would take off like this! I keep meaning to rewrite this blog from scratch, since I’ve gone from novice to professional since writing these posts, fixed a lot of mistakes and have better ways of doing all of this now, but the game takes up all my time!
Getting close to release, so maybe when it’s all over I can come back and start a new series of tutorials.
I still don’t think it’s all fully settled in – the Kickstarter success, the worldwide news coverage, fanmail from SEGA employees, even having an almost-finished complete game is hard for my brain to believe. Maybe I’ll wake up soon and this will all have been a dream!
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!
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!!!!
I meant to say “when assembling”. lol
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 🙂
Hi,
I can build without errors with “mame64 genesis -cart test.bin” , but when i try to debug(mame64 genesis -cart test.bin -debug) i get access violation errors ? Can you help me
Hi there! I’m not sure on that one, sounds like a Mame error rather than anything to do with your ROM. I’d suggest trying Regen instead, I’ve had far more luck with it recently, and it has a much better debugger: http://segaretro.org/Regen
Thanks for quick reply,It was caused by indentation problem at tabs as you mentioned in article.I’ll try regen.Good work! This is really good excercise for my cs lessons 🙂
Hello, I can’t seem to find a way to compile multiple files at once. Any ideas?
Hey Bobi, I haven’t done this yet but I _think_ you need to use some sort of batch script to stick all the separate files together into one big file which then gets compiled, rather than compile each separate file on it’s own. I think the PONG example will cover it so try there.
Finding out about Tanglewood really sparked an interest to attempt my own game. I’m really interested in 32X/CD or Saturn, but that may be too extreme? I guess I should start here, finding where to begin has been a challenge.