loading...
All Genesis articles | Back to top

SEGA Genesis: Building a ROM

This first article is focused on building a very simple ROM for the SEGA Genesis using assembly code. The ROM does a simple graphical effect, flickering the background color. Getting text on the screen is more complicated, so a proper 'Hello World' ROM will have to wait.

Genesis Hardware

SEGA Megadrive PAL Model 1

The SEGA Genesis had a Motorola 68000 CPU running at 7.6 MHz, a pretty fast processor for its time. It had very limited memory, only 64 KB main RAM and 64 KB Video RAM, half of what a decent home computer of the time would have, but that would be offset by the storage device being ROM cartridges with 512 KB - 8 MB, reducing any load time to almost zero.

The Genesis had a secondary CPU, a Zilog Z80 running at 3.58 MHz with 8 KB RAM. The Z80 was a very popular CPU in the late 70s and early 80s, used in the ZX Spectrum, the MSX computers, the Sega Master System, and in arcade games such as Pac-Man, Galaga, Dig Dug, Xevious, and others.

The Z80 was primarily used for controlling the audio chips, a Yamaha YM2612 FM chip which delivered 6 stereo FM channels and the Texas Instruments SN76489 Programmable Sound Generator (PSG), which has 3 square waves and a noise channel.

For this article, we're going to focus on the Motorola 68000.

The Motorola 68000

Motorola 68000

The Motorola 68000 is a 16/32-bit processor introduced in 1979. It was very popular in 80s computers, notably:

It was also a popular CPU for arcade games:

For the 1988 Genesis, the MC68000 was an obvious choice, it was already a mainstay in home computers, and SEGA already used it their System 16 arcade hardware, presumably making it much easier to port arcade games to the home market. In fact, the Genesis architecture is very similar to the System 16, which also features a Z80, a Yamaha FM chip, and a standard resolution of around 320x224.

Markey Jester's Motorola 68000 Beginner's Tutorial is useful for learning MC68000 assembly code, the game development language of choice for SEGA Genesis programmers.

If you want to test out some 68000 instructions or debug your code, EASy68K is really useful. It's an integrated development environment (IDE) that includes an editor, an assembler, and a debugger. It only emulates a 68000, no Genesis-specific stuff at all. It does have features for rendering text, graphics, and sound, so it can be a great test bed for certain types of problemsolving.

Tools

The only tool needed to create a ROM is an MC68000 assembler and a Z80 assembler. The open source assembler vasm works well for this purpose.

You should download these two builds:

We can test the MC68000 assembler with a simple program:

    move.w #7,d0

A single instruction that sets register D0 to the value 7.

Assemble it using this command:

vasmm68k_mot -Fbin test.asm -o test.bin

The result is a 4 byte file test.bin:

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000  30 3C 00 07                                      0<..

Generated by HxD

The Code

This ROM is a very simple example that flashes the background color and does nothing else. The main loop looks like this:

__main
    move.w  #0,d0                  ; d0 = 0
    move.w  #$8F00,vdp_control     ; Graphics init code
                                   ; (disable auto-increment)
    move.l  #$C0000003,vdp_control ; Set up write to color 0
loop
    move.w  d0,vdp_data            ; Write color 0 value
    add.w   #1,d0                  ; Modify color

    move.w  #100,d1                ; Busy-wait a little while
.wait                              ; by counting down from 100
    dbra    d1,.wait

    jmp     loop                   ; Goto loop

Note that vdp_control and vdp_data are constant addresses, used for memory-mapped I/O between the MC68000 and the VDP graphics processor. The color cycling is implemented by incrementing the D0 register and writing that value to the COLOR 0 memory of the VDP. This 16-bit value is interpreted as a 9-bit color value, selecting a color from 512 possible colors. The 16-bit value is interpreted like this:

Bit #: FEDCBA9876543210
Color: ....BBB.GGG.RRR.

So that's it - incrementing this 16-bit value will change the color and it will eventually wrap around to 0, starting over with the color black.

Now, to make a functional ROM out of this program, we need the following extra stuff:

The following is the assembly code for a very minimal functional ROM containing the above main loop. The details of the initialization code is explained in a later article.

; ------------------------------------------------------------------------------
;
; Copyright 2022 Nameless Algorithm
; See https://namelessalgorithm.com/ for more information.
;
; LICENSE
; You may use this source code for any purpose. If you do so, please attribute
; 'Nameless Algorithm' in your source, or mention us in your game/demo credits.
; Thank you.
;
; ------------------------------------------------------------------------------


; ROM HEADER
; ------------------------------------------------------------------------------
rom_header:
    dc.l   $00FFFFFE        ; Initial stack pointer value
    dc.l   EntryPoint       ; Start of program
    dc.l   ignore_handler   ; Bus error
    dc.l   ignore_handler   ; Address error
    dc.l   ignore_handler   ; Illegal instruction
    dc.l   ignore_handler   ; Division by zero
    dc.l   ignore_handler   ; CHK exception
    dc.l   ignore_handler   ; TRAPV exception
    dc.l   ignore_handler   ; Privilege violation
    dc.l   ignore_handler   ; TRACE exception
    dc.l   ignore_handler   ; Line-A emulator
    dc.l   ignore_handler   ; Line-F emulator
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Spurious exception
    dc.l   ignore_handler   ; IRQ level 1
    dc.l   ignore_handler   ; IRQ level 2
    dc.l   ignore_handler   ; IRQ level 3
    dc.l   ignore_handler   ; IRQ level 4 (horizontal retrace interrupt)
    dc.l   ignore_handler   ; IRQ level 5
    dc.l   ignore_handler   ; IRQ level 6 (vertical retrace interrupt)
    dc.l   ignore_handler   ; IRQ level 7
    dc.l   ignore_handler   ; TRAP #00 exception
    dc.l   ignore_handler   ; TRAP #01 exception
    dc.l   ignore_handler   ; TRAP #02 exception
    dc.l   ignore_handler   ; TRAP #03 exception
    dc.l   ignore_handler   ; TRAP #04 exception
    dc.l   ignore_handler   ; TRAP #05 exception
    dc.l   ignore_handler   ; TRAP #06 exception
    dc.l   ignore_handler   ; TRAP #07 exception
    dc.l   ignore_handler   ; TRAP #08 exception
    dc.l   ignore_handler   ; TRAP #09 exception
    dc.l   ignore_handler   ; TRAP #10 exception
    dc.l   ignore_handler   ; TRAP #11 exception
    dc.l   ignore_handler   ; TRAP #12 exception
    dc.l   ignore_handler   ; TRAP #13 exception
    dc.l   ignore_handler   ; TRAP #14 exception
    dc.l   ignore_handler   ; TRAP #15 exception
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    dc.l   ignore_handler   ; Unused (reserved)
    
    dc.b "SEGA GENESIS    " ; Console name
    dc.b "(C) NAMELESS    " ; Copyright holder and release date
    dc.b "VERY MINIMAL GENESIS CODE BY NAMELESS ALGORITHM   " ; Domest. name
    dc.b "VERY MINIMAL GENESIS CODE BY NAMELESS ALGORITHM   " ; Intern. name
    dc.b "2022-08-04    "   ; Version number
    dc.w $0000              ; Checksum
    dc.b "J               " ; I/O support
    dc.l $00000000          ; Start address of ROM
    dc.l __end              ; End address of ROM
    dc.l $00FF0000          ; Start address of RAM
    dc.l $00FFFFFF          ; End address of RAM
    dc.l $00000000          ; SRAM enabled
    dc.l $00000000          ; Unused
    dc.l $00000000          ; Start address of SRAM
    dc.l $00000000          ; End address of SRAM
    dc.l $00000000          ; Unused
    dc.l $00000000          ; Unused
    dc.b "                                        " ; Notes (unused)
    dc.b "JUE             "                         ; Country codes
        


; CONSTANTS
; ------------------------------------------------------------------------------
vdp_control     = $C00004 ; Memory mapped I/O
vdp_data        = $C00000 ;



; INIT
; ------------------------------------------------------------------------------
EntryPoint:               ; Entry point address set in ROM header
    move    #$2700,sr     ; disable interrupts


; Skip clear RAM - we don't use RAM at all in this example

TMSS
    move.b  $00A10001,d0      ; Move Megadrive hardware version to d0
    andi.b  #$0F,d0           ; The version is stored in last four bits,
                              ; so mask it with 0F
    beq     .skip             ; If version is equal to 0,skip TMSS signature
    move.l  #'SEGA',$00A14000 ; Move the string "SEGA" to $A14000
.skip

Z80
    move.w  #$0100,$00A11100 ; Request access to the Z80 bus
    move.w  #$0100,$00A11200 ; Hold the Z80 in a reset state
.wait
    btst    #$0,$00A11101    ; Check if we have access to the Z80 bus yet
    bne     .wait            ; If we don't yet have control,branch back up to Wait
    move.l  #$00A00000,a1    ; Copy Z80 RAM address to a1
    move.l  #$00C30000,(a1)  ; Copy data,and increment the source/dest addresses
 
    move.w  #$0000,$00A11200 ; Release reset state
    move.w  #$0000,$00A11100 ; Release control of bus
 
; Initialize PSG to silence
    ;move.l  #$9fbfdfff,$00C00011  ; silence

init_vdp
    move.l  #VDPRegisters,a0 ; Load address of register table into a0
    move.l  #$18,d0          ; 24 registers to write
    move.l  #$00008000,d1    ; 'Set register 0' command
                             ; (and clear the rest of d1 ready)
 
copy_vdp
    move.b  (a0)+,d1         ; Move register value to lower byte of d1
    move.w  d1,$00C00004     ; Write command and value to VDP control port
    add.w   #$0100,d1        ; Increment register #
    dbra    d0,copy_vdp

; Ignore I/O ports - we don't use them
    



; MAIN PROGRAM
; ------------------------------------------------------------------------------
main
    move.w  #0,d0
    move.w  #$8F00,vdp_control     ; Set VDP autoincrement to 2 words/write
    move.l  #$C0000003,vdp_control ; Set up VDP to write to CRAM address $0000
loop
    move.w  d0,vdp_data        ; black (BGR)
    add.w   #1,d0
    move.w  #100,d1
.wait
    dbra    d1,.wait
    jmp     loop



; EXCEPTION AND INTERRUPT HANDLERS
; ----------------------------------------------------------------------------
    align 2 ; word-align code

ignore_handler
    rte ; return from exception (seems to restore PC)



; VDP REGISTER INITIALIZATION
; ------------------------------------------------------------------------------
; - Explanations (albeit short explanations) of the VDP registers can be found
;   in chapter 4 of the SEGA2 doc 

    align 2 ; word-align code

VDPRegisters:
; Mode
reg00:  dc.b %00000101  ; MD colors on, display on, hblank off
reg01:  dc.b %01000100  ; Display on, vblank off, DMA off, NTSC, Genesis mode

; VRAM layout
reg02:  dc.b $38        ; Pattern table for Scroll Plane A at VRAM $E000
reg03:  dc.b $00        ; Pattern table for Window Plane   at VRAM $0000
reg04:  dc.b $07        ; Pattern table for Scroll Plane B at VRAM $E000
reg05:  dc.b $78        ; Sprite table at VRAM $F000 (bits 0-6 = bits 9-15)
reg06:  dc.b $00        ; Sprite table 128KB VRAM (ignore)

; BG color
reg07:  dc.b $00        ; Background colour - bits 0-3 = colour
                        ;                     bits 4-5 = palette

; Unused
reg08:  dc.b $00        ; SMS HScroll reg
reg09:  dc.b $00        ; SMS VScroll reg

; General
reg0A:  dc.b $00        ; hblank counter (# scanlines between hblank)

reg0B:  dc.b %0000000   ; Ext. interrupts off, HScroll fullscreen
reg0C:  dc.b %0000000   ; H40 (320px) mode, no external pixel bus, disable 
                        ; shadow/highlight mode, no interlace
reg0D:  dc.b $3F        ; Horiz. scroll table at VRAM $FC00 (bits 0-5)
reg0E:  dc.b $00        ; Unused
reg0F:  dc.b $02        ; Autoincrement 2 bytes
reg10:  dc.b $00        ; Vert. scroll 32, Horiz. scroll 32

; Window
reg11: dc.b $00         ; Window Plane X pos 0 left
                        ; (pos in bits 0-4, left/right in bit 7)
reg12: dc.b $00         ; Window Plane Y pos 0 up
                        ; (pos in bits 0-4, up/down in bit 7)

; DMA
reg13: dc.b $00         ; DMA length lo byte
reg14: dc.b $00         ; DMA length hi byte
reg15: dc.b $00         ; DMA source address lo byte
reg16: dc.b $00         ; DMA source address mid byte
reg17: dc.b $80         ; DMA source address hi byte,
                        ; memory-to-VRAM mode (bits 6-7)

__end:

Running the ROM

Color Cycling

Building the ROM is as simple as assembling a raw binary file:

vasmm68k_mot -Fbin minimal.asm -o roms\minimal.gen 

Which results in this ROM file:

Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000  00 FF FF FE 00 00 02 04 00 00 02 98 00 00 02 98  .ÿÿþ.......˜...˜
00000010  00 00 02 98 00 00 02 98 00 00 02 98 00 00 02 98  ...˜...˜...˜...˜
00000020  00 00 02 98 00 00 02 98 00 00 02 98 00 00 02 98  ...˜...˜...˜...˜
00000030  00 00 02 98 00 00 02 98 00 00 02 98 00 00 02 98  ...˜...˜...˜...˜
00000040  00 00 02 98 00 00 02 98 00 00 02 98 00 00 02 98  ...˜...˜...˜...˜
00000050  00 00 02 98 00 00 02 98 00 00 02 98 00 00 02 98  ...˜...˜...˜...˜
00000060  00 00 02 98 00 00 02 98 00 00 02 98 00 00 02 98  ...˜...˜...˜...˜
00000070  00 00 02 98 00 00 02 98 00 00 02 98 00 00 02 98  ...˜...˜...˜...˜
00000080  00 00 02 98 00 00 02 98 00 00 02 98 00 00 02 98  ...˜...˜...˜...˜
00000090  00 00 02 98 00 00 02 98 00 00 02 98 00 00 02 98  ...˜...˜...˜...˜
000000A0  00 00 02 98 00 00 02 98 00 00 02 98 00 00 02 98  ...˜...˜...˜...˜
000000B0  00 00 02 98 00 00 02 98 00 00 02 98 00 00 02 98  ...˜...˜...˜...˜
000000C0  00 00 02 98 00 00 02 98 00 00 02 98 00 00 02 98  ...˜...˜...˜...˜
000000D0  00 00 02 98 00 00 02 98 00 00 02 98 00 00 02 98  ...˜...˜...˜...˜
000000E0  00 00 02 98 00 00 02 98 00 00 02 98 00 00 02 98  ...˜...˜...˜...˜
000000F0  00 00 02 98 00 00 02 98 00 00 02 98 00 00 02 98  ...˜...˜...˜...˜
00000100  53 45 47 41 20 47 45 4E 45 53 49 53 20 20 20 20  SEGA GENESIS    
00000110  28 43 29 20 4E 41 4D 45 4C 45 53 53 20 20 20 20  (C) NAMELESS    
00000120  56 45 52 59 20 4D 49 4E 49 4D 41 4C 20 47 45 4E  VERY MINIMAL GEN
00000130  45 53 49 53 20 43 4F 44 45 20 42 59 20 4E 41 4D  ESIS CODE BY NAM
00000140  45 4C 45 53 53 20 41 4C 47 4F 52 49 54 48 4D 20  ELESS ALGORITHM 
00000150  20 20 56 45 52 59 20 4D 49 4E 49 4D 41 4C 20 47    VERY MINIMAL G
00000160  45 4E 45 53 49 53 20 43 4F 44 45 20 42 59 20 4E  ENESIS CODE BY N
00000170  41 4D 45 4C 45 53 53 20 41 4C 47 4F 52 49 54 48  AMELESS ALGORITH
00000180  4D 20 20 20 32 30 32 32 2D 30 38 2D 30 34 20 20  M   2022-08-04  
00000190  20 20 00 00 4A 20 20 20 20 20 20 20 20 20 20 20    ..J           
000001A0  20 20 20 20 00 00 00 00 00 00 02 B4 00 FF 00 00      .......´.ÿ..
000001B0  00 FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00  .ÿÿÿ............
000001C0  00 00 00 00 00 00 00 00 00 00 00 00 20 20 20 20  ............    
000001D0  20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20                  
000001E0  20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20                  
000001F0  20 20 20 20 4A 55 45 20 20 20 20 20 20 20 20 20      JUE         
00000200  20 20 20 20 46 FC 27 00 10 39 00 A1 00 01 02 00      Fü'..9.¡....
00000210  00 0F 67 0A 23 FC 53 45 47 41 00 A1 40 00 33 FC  ..g.#üSEGA.¡@.3ü
00000220  01 00 00 A1 11 00 33 FC 01 00 00 A1 12 00 08 39  ...¡..3ü...¡...9
00000230  00 00 00 A1 11 01 66 F6 22 7C 00 A0 00 00 22 BC  ...¡..fö"|. .."¼
00000240  00 C3 00 00 33 FC 00 00 00 A1 12 00 33 FC 00 00  .Ã..3ü...¡..3ü..
00000250  00 A1 11 00 41 FA 00 46 70 18 22 3C 00 00 80 00  .¡..Aú.Fp."<..€.
00000260  12 18 33 C1 00 C0 00 04 D2 7C 01 00 51 C8 FF F2  ..3Á.À..Ò|..QÈÿò
00000270  30 3C 00 00 33 FC 8F 00 00 C0 00 04 23 FC C0 00  0<..3ü...À..#üÀ.
00000280  00 03 00 C0 00 04 33 C0 00 C0 00 00 52 40 32 3C  ...À..3À.À..R@2<
00000290  00 64 51 C9 FF FE 60 EE 4E 73 00 00 05 44 38 00  .dQÉÿþ`îNs...D8.
000002A0  07 78 00 00 00 00 00 00 00 3F 00 02 00 00 00 00  .x.......?......
000002B0  00 00 00 80                                      ...€

Generated by HxD

It can be run using the emulator mednafen:

mednafen minimal.gen 
Running on a real SEGA Genesis

Testing the ROM on a real SEGA Genesis is also possible using the Mega EverDrive X5, a cartridge that enables running any SEGA Genesis or even SEGA Master System ROM on a real SEGA Genesis console.

Building vasm from Source

If you desire to build vasm from source, you can build the MC68000 and Z80 assemblers using the following instructions.

VASM is distributed with a Makefile, which can be built with Visual Studio's nmake tool. To build it, start the Visual Studio 'Developer Command Prompt' and enter the directory containing the vasm source, and then execute these commands:

makedir obj_win32
nmake -f Makefile.Win32 CPU=m68k SYNTAX=mot

This generates the executable vasmm68k_mot_win32.exe, which can be used to create a ROM file.

References

Tanglewood

For the SEGA Genesis specifics, Matt Phillips's blog bigevilcorporation.co.uk was very helpful. He's working on a new Genesis game, Tanglewood, which looks like a very cool project.

SGI and Sun workstations:

Tools:

Emulators:

References:

Hardware

Tutorials:

General SEGA Genesis info: