SEGA Genesis: Building a ROM

Contra: Hard Corps

I recently ordered the Mega EverDrive X5, a cartridge that enables running any SEGA Genesis or even SEGA Master System ROM on a real SEGA Genesis console. This inspired me to try creating a Genesis ROM of my own for that great 1988 console.

Genesis Hardware

The SEGA Genesis has a Motorola 68000 CPU running at 7.6 MHz, 64 KB main RAM, 64 KB Video RAM, a Z80 at 3.58 MHz with 8 KB audio RAM for controlling the audio chips, an Yamaha YM2612 FM chip and the Texas Instruments SN76489 Programmable Sound Generator (PSG).

For now, I have focused on the 68000, and getting a 'hello world' ROM up and running.

Reference: Wikipedia:Sega Genesis

Getting Started

Having done a little bit of Amiga programming, the Motorola 68000 was not entirely foreign to me, so the trickiest part of making a Genesis ROM was understanding the particulars of the Genesis hardware.

I looked at a few different tutorials for getting started, but Matt Phillips's blog bigevilcorporation.co.uk ended up being the most helpful. He's working on a new Genesis game, Tanglewood, which looks like a very cool project.

Other references:

Hello World

Hello World

Based on Lionel Sanderson's revision of Matt Phillips' example code, I made a simple animated text output example with vertical sync.

The font bitmap data in Matt's example only had the letters 'DEHLORW', so I ended up writing my own font. It isn't pretty, but it'll do.


I added this text rendering loop to the example initialization code, it renders the text 'SYLTEFAR SAYS HELLO ' 92 times with an offset that changes every frame:

vdp_control = $C00004

    move.w #0,d6        ; d6 = offset

MainLoop:
    lea Text,a0         ; a0 = text ptr
    add.w d6,a0         ; add offset (animation)
    move.w #92,d1       ; d1 = 92 (text repeat count)
    move.l #$40000003,$00C00004
                        ; Set up VDP to write to VRAM
                        ; address $C000 (Plane A)
.loop:
    clr.w d0            ; d0 = 0
    move.b (a0)+,d0     ; set lower byte to character
    sub.b #$40,d0       ; subtract (ASCII value of 'A' - 1)
                        ; 'A' becomes 1, 'B' becomes 2, etc.
    move.w d0,$00C00000 ; write to VDP
    cmp #TextEnd,a0     ; repeat until end of string
    bne .loop
    lea Text,a0         ; reset
    dbra d1,.loop       ; repeat d1 times

    jsr WaitVBlankEnd   ; Vertical Sync

    move.b #0,d5        ; d5 = 0
    add.w #1,d6         ; d6 += 1
    cmp.b #9,d6         ; d6 = 9 ?
    bne MainLoop
    clr.w d6            ; if so, d6 = 0
    jmp MainLoop        ; next iteration

WaitVBlankEnd:
    move.w vdp_control,d0  ; Move VDP status word to d0
    andi.w #$0008,d0       ; AND with bit 4 (vblank),
                           ; result in status register
    beq    WaitVBlankEnd   ; Branch if equal (to zero)
    rts

Text:
    dc.b "SYLTEFAR SAYS HELLO "
TextEnd:

A particular line of code of the original example caused problems for me:

;=========================================
;8. Clearing the Registers and Tidying Up
;=========================================
move.l #$00000000,a0    ; Move $0 to a0
movem.l (a0),d0-d7/a1-a7 ; Multiple move 0 to all registers

This seems like a mistake. The instruction:

MOVEM <ea>,list ; Source -> Listed Registers

Copies the contents of 'ea' to the listed registers. In the example code, A0 is set to 0, which, according to sega2.doc points to the start of the ROM, which contains random stuff. Specifically, the stack pointer was overwritten, causing subroutines to return to bad locations.

After removing that line, the example code worked just fine.

Tools

I chose to use the open source vasm assembler, built with Motorola-style syntax. Building the ROM was as simple as outputting a raw binary file:

vasm -Fbin main.asm -o roms\test1.gen

For testing, I started out using the great emulator mednafen. Starting mednafen from the command-line with the debugger enabled and paused on the first instruction can be done like this:

mednafen -debugger.autostepmode 1 roms\test1.gen

A few important mednafen debug commands:

R                      Run
S                      Step
Up/Down, Page Up/Down  Navigate code
Space                  Set breakpoint
Alt+1                  CPU debugger view
Alt+3                  Memory view

See Mednafen Debugger Documentation.

Debugging the stack pointer problem mentioned above, I had to inspect the stack pointer for corruption, which doesn't seem to appear in the mednafen debugger. To solve this problem, I switched to using the MAME debugger, which is more full-featured and shows all the registers.

MAME Genesis debug command-line (pauses on first instruction):

mame64 genesis -debug -window -cart roms\test1.gen

A few MAME debugger hotkeys:

F10     Step over
F11     Step into
Ctrl-M  Show memory

And commands:

go [ADDR]  Run until reaching ADDR
help       The most important command of all