Now write your own.
The other courses in this collection took the Sega Genesis apart — the 68000, the Z80, the VDP, the sound chips — and showed how emulators put it back together. This course closes the loop: you write code and the real machine runs it. It covers the free, open toolkit the community built around SGDK and the m68k-elf-gcc cross-compiler, the cartridge ROM format every retail game also uses, full setup for Windows, macOS and Linux, and every step from an empty folder to a playable program in BlastEm — and on a real console through a flashcart. You'll need a computer, a little patience with C, and nothing from Sega at all.
Getting started
What homebrew is (and isn't), the SGDK toolkit and the 68000 cross-compiler, and how to install it all on your own machine.
Building a game
Hello-world on the VDP, the build pipeline and the ROM header, tiles, sprites, FM/PSG sound, the controller, and the 60 Hz loop.
Running it
The tight BlastEm development loop, running on a real Genesis through a Mega EverDrive, and where to take your skills next.
Getting started
Before touching a compiler, you should know what you're joining. This first stretch explains what console homebrew actually is, how a legal, Sega-free toolchain for a 1988 console can even exist, and how to install it on your computer. Everything here connects backwards to the other courses: the machine you're about to program is the one they dissected.
What homebrew is (and isn't)
Homebrew is software for a games console written by people outside the console maker's licensed-developer program. No official development kit, no signed contract, no access to Sega's tools or documentation — just the machine itself, publicly available knowledge about how it works, and a toolchain built by volunteers. The word covers everything from a scrolling "hello world" to full games, demos, chiptune players and cartridge utilities. The Genesis is one of the friendliest consoles ever made for this: it never had a serious lockout on cartridges, its hardware is exhaustively documented, and a modern C toolkit — SGDK — makes it approachable in an afternoon.
Draw the line clearly, because the internet often smudges it: homebrew is writing your own software. It is not copying games, and this course has nothing to do with piracy. You'll build programs from your own source code with a toolchain containing no Sega code whatsoever, and you'll run them either in an emulator or on a flashcart designed to load your own ROM images. (The usual disclaimer: this is an educational overview, not legal advice, and details vary by jurisdiction — but "I wrote a program and ran it on hardware I own" is about as clean as the hobby gets.)
A licensed developer rents the kitchen with the chef's blessing: recipe book, branded ingredients, a hotline to the manufacturer. A homebrew developer owns the same kitchen — they bought it — but arrives with no recipe book at all. Everything they know about the oven's quirks was worked out by a community of cooks comparing notes for decades. The food is entirely theirs; only the kitchen is Sega's design. Nobody is stealing meals.
How a 1988 console stays programmable
The Genesis launched with cartridges as its only medium, and — unlike its rivals — never enforced a strong cryptographic lockout on them. Later models added TMSS, a light trademark check at boot (the reason your ROM header must begin with "SEGA"), but that is a formality any homebrew ROM satisfies for free. So the path for hobbyist code has always been open: build a ROM image, put it on a cartridge you can rewrite, and the console runs it. The scene's real work was the other half — a toolchain. Volunteers ported GCC — the free C/C++ compiler — to target the Genesis's Motorola 68000, and built libraries and asset tools on top. That effort's most polished form is SGDK, the kit this course teaches.
What you can realistically build
Everything the machine can do is on the table, because homebrew code runs with full control of the hardware — there is no operating system underneath you (a fact Module 10 turns into a superpower). The scene has produced platformers, shmups, puzzle games, RPGs, chiptune demos and hardware test ROMs. Your ceiling is the console's: the 68000 at 7.67 MHz, the Z80 sound coprocessor, the VDP's tile-and-sprite engine, the YM2612 and PSG for sound, and 64 KB of work RAM. Those courses taught you the machine as an examiner; this one hands you the keys.
- Homebrew = your own software for the console, built without Sega's SDK or blessing — a separate thing from piracy entirely.
- The Genesis never locked its cartridges; TMSS on later models is a trademark check your ROM header satisfies for free.
- The scene's pillar is a free toolchain — SGDK on top of m68k-elf-gcc — with no Sega code in it.
- You need no special hardware to start: SGDK plus an emulator like BlastEm is a complete development loop.
The toolchain: SGDK & m68k-elf-gcc
Your computer and the Genesis speak different machine languages. Your laptop's processor executes x86-64 or ARM instructions; the Genesis's 68000 executes Motorola 68000 machine code (the CPU course spent a long time on it). A normal compiler produces programs for the machine it runs on. What you need is a cross-compiler: a compiler that runs on your machine but emits code for a different one. This is utterly routine in embedded development — it's how router firmware and spacecraft code get built — and it's the single concept that makes console homebrew possible.
A cross-compiler is a translator living in your city who writes flawless letters in a language spoken only on a distant island. The translator never needs to visit the island; they just need to know the language perfectly. Your PC never executes a single 68000 instruction during a build — it only writes them, and the finished ROM is posted to the console afterwards.
The pieces, by name
The modern Genesis toolchain is bundled as SGDK — the Sega Genesis Development Kit, an open-source project created and maintained by Stéphane Dallongeville. You'll see these names constantly, so meet the cast once, properly:
libmd), a set of asset tools, sample projects, and the makefiles that tie it all together. Everything below ships inside it.m68k-elf — bare-metal Motorola 68000 with no operating system assumed. With it come the GNU binutils (m68k-elf-as the assembler, m68k-elf-ld the linker, m68k-elf-objcopy, objdump)..res script listing your images, palettes, tilemaps and music, and turns each into 68000-ready data with C symbols you can reference. Module 05 watches it work.
Every tool in the toolchain carries the target's name as a prefix:
the compiler is m68k-elf-gcc, the linker
m68k-elf-ld, the object inspector
m68k-elf-objdump. The prefix is your constant reminder
that outputs are for the console, not for the machine you're sitting
at. SGDK's makefiles pass the 68000-appropriate flags for you
(-m68000, size-and-speed optimisation, the right memory
layout) — you rarely type them yourself.
What "no operating system" means
The m68k-elf target means the compiler assumes a
bare-metal environment: no Linux, no Windows, no
system calls to lean on. When the console powers on it reads the
68000 vector table
at address 0 of your ROM, loads the stack pointer and jumps to the
reset vector — which is SGDK's startup code. That code clears RAM,
sets up the VDP, initialises libmd, and then calls your
main(). There is no printf to a terminal
here; "output" means drawing tiles on the screen, which is exactly
what Module 04 does.
Nothing in this toolchain is Sega's. GCC and binutils are GNU projects; SGDK and its libraries were written by the community and are open source. This matters legally (Module 01) but also practically: you can read every line of the code your program is built from, all the way down to the reset vector.
- A cross-compiler runs on your machine but emits code for another — here,
m68k-elf-gccemitting 68000 machine code. - SGDK bundles the compiler, binutils, the
libmdruntime,rescomp, the XGM sound driver and sample projects. - The target is bare metal: your ROM begins at the 68000 vector table, and SGDK's startup code runs before
main(). - SGDK and everything under it is open source and free of Sega code — legally clean and completely readable.
Setup — Windows, macOS or Linux
SGDK is developed on Windows and ships a ready-made toolchain there, but it runs everywhere — the sanctioned cross-platform route is a Docker image that bundles the exact compiler and tools, so your build is identical on any OS. The shape is always the same: (1) get SGDK, (2) make sure the build can find it, (3) build a sample to prove the chain works, and (4) install BlastEm as your test console. Pick your OS below — the page tries to guess it — and note that details drift over time: if anything here disagrees with SGDK's own README, trust the README; the shape of the process is what this module teaches.
Windows is SGDK's native home: a release ships with the m68k toolchain, the tools and the library already built. You just unzip it and point one environment variable at it.
- Download the latest SGDK release from its GitHub releases page and unzip it to a path with no spaces, e.g.
C:\sgdk. - Set an environment variable
GDKto that folder (GDK=C:\sgdk). - Build from a project folder using the
makethat ships inside SGDK.
:: point GDK at your unzipped SGDK $ set GDK=C:\sgdk :: build the current project with SGDK's bundled make $ %GDK%\bin\make -f %GDK%\makefile.gen compiling main.c ... linking ... rom.out output ... rom.bin
Those last two lines are the whole course in miniature — an ELF
(rom.out) and a raw ROM (rom.bin), both
of which Module 05 will teach you to read.
On macOS (Intel or Apple Silicon) the clean route is the
official Docker image: it carries the exact
m68k-elf-gcc, rescomp and library, so
you never build a toolchain by hand. Install
Docker Desktop, then run
the build from your project directory:
# from inside your project folder (contains src/ and res/) $ docker run --rm -v "$PWD":/src sgdk # ↑ mounts the current folder as /src and runs SGDK's make inside compiling main.c ... linking ... rom.out output ... rom.bin
You build the sgdk image once from SGDK's included
Dockerfile (docker build -t sgdk . in
the SGDK folder). After that, the one docker run
line above is your entire build command — no PATH surgery, no
Homebrew formula to keep current.
Linux uses the same Docker route as macOS — the most reliable way to get a matching toolchain — and it's the same one command everywhere:
# 1) build the image once, from the SGDK checkout: $ git clone https://github.com/Stephane-Dallongeville/SGDK $ cd SGDK && docker build -t sgdk . # 2) then, from any project folder: $ docker run --rm -v "$PWD":/src sgdk output ... rom.bin
If you prefer a native install, the community also maintains
build scripts (e.g. the Gendev / Marsdev projects)
that compile m68k-elf-gcc and SGDK straight onto
your system and export a GDK variable — handy for
CI or an editor's build task. Docker is simply the least
fiddly.
Prove it works — build a sample
Never trust an install you haven't built with. SGDK ships a
sample/ tree; copy one out, build it, and you have a
rom.bin to open in an emulator:
$ cp -r $GDK/sample/basics/hello-world ~/md-hello $ cd ~/md-hello $ make # (or the docker run line for your OS) rescomp resources.res ... compiling main.c ... output ... out/rom.bin
Last step: install BlastEm (from
retrodev.com; it
runs on all three OSes) and drag the freshly built
rom.bin onto it. Text on screen — a running Genesis
program you built — is the finish line for this module.
The classic first-day failures are a GDK variable that
points at the wrong folder, or a project path with spaces in it
(the 68000 makefiles dislike them). A correct GDK, a
space-free path, and a fresh shell fix virtually every early
problem.
- Windows gets a prebuilt SGDK release; macOS and Linux use the official Docker image for an identical toolchain.
- Point the build at SGDK via the
GDKvariable (Windows) or a mounted volume (Docker), then just runmake. - Build a bundled sample to a
rom.binbefore writing your own — it proves the whole chain. - Install BlastEm as your test console; SGDK's README is the authority when details drift.
Building a game
Vocabulary over; hands on. This is the heart of the course: the traditional first program on the VDP, the pipeline that turns your source into a cartridge image and the header that image carries, then the four pillars of any real game — tiles, sprites, sound and input — and the 60 Hz loop that binds them to the display's clock. By the end you will have watched code you compiled boot on an (emulated) Genesis. That moment does not get old.
Hello, world
Here is the traditional first program in SGDK. It is startlingly
short, because SGDK's startup code has already booted the machine
before your main() runs — cleared RAM, initialised the
VDP, loaded a default font into video memory. All that's left for
you is to say your line:
#include <genesis.h> // SGDK's single front-door header int main() { // SGDK already initialised the VDP and uploaded a font. // VDP_drawText writes tile indices into plane A's tilemap; // (column, row) are in 8x8 tiles, so (10, 12) is near centre. VDP_drawText("HELLO, WORLD", 10, 12); while (1) { SYS_doVBlankProcess(); // wait for vblank; run SGDK's per-frame work } return 0; }
Every line is doing something specific
#include <genesis.h>— SGDK's umbrella header, pulling in the VDP, sprite, input, sound and DMA APIs in one line.VDP_drawText— the console has no text mode. This helper looks up each character in the font SGDK uploaded to VRAM, then writes the matching tile index into plane A's tilemap at the (column, row) you gave — in 8×8-tile units, not pixels.SYS_doVBlankProcess— blocks until the VDP finishes drawing the frame (the vertical-blank interrupt), then runs SGDK's own per-frame housekeeping: DMA queue flush, sprite list upload, input refresh, sound driver sync. It is the heartbeat Module 10 is built on.
Notice what is absent: no framebuffer, no pixel plotting. The Genesis is a tile machine. The screen is a grid of 8×8 tiles; "drawing text" means choosing which tile sits in which grid cell. Understanding that one fact is most of understanding the VDP, and Module 06 builds on it directly.
A PC framebuffer is a blank canvas — paint any pixel any colour.
The Genesis screen is a post-office wall of pigeonholes, each
holding one 8×8 stamp from a book of stamps (the tileset in VRAM).
You don't paint the wall; you decide which stamp goes in each hole.
VDP_drawText just looks up the letter-stamps and slots
them into a row of holes. Everything the VDP does — backgrounds,
text, even sprites — is this same trick of arranging pre-drawn
stamps.
The lab below dramatises the first boot on a mock TV: a generic red boot wordmark (a clearly non-Sega stand-in — we ship no copyrighted logo or jingle), then the VDP comes up black, then your text is laid into the tile grid one cell at a time, then the vblank loop counts frames at 60 Hz until you press START. Change the message — it's your program now.
VDP_drawText laying tiles into plane A → the SYS_doVBlankProcess loop. Each glyph occupies one highlighted 8×8 tile cell.
- SGDK's startup code boots the machine before
main(); your first program is essentially one draw call and a loop. - The Genesis has no text mode and no framebuffer —
VDP_drawTextplaces font tiles into plane A's tilemap. - Coordinates are in 8×8 tiles, not pixels: the whole screen is a grid of tile cells.
SYS_doVBlankProcess()is the per-frame heartbeat; everything in Module 10 grows from it.
The build pipeline & the ROM header
Typing make in Module 03 fired a multi-stage pipeline.
Most of it is the standard C build everyone half-remembers, plus two
Genesis-specific steps: a resource compiler for your art and music,
and a final conversion to a raw cartridge image.
- Compile.
m68k-elf-gcc -cturns each.cfile into a.oobject of 68000 machine code with unresolved references ("I call something namedVDP_drawText, wherever that is"). - Resource-compile.
rescompreads yourresources.resand converts each listed asset — a PNG tileset, a palette, a tilemap, an XGM track — into a data object with C symbols you can reference by name. - Link.
m68k-elf-ldfuses your objects and the resource objects with SGDK's runtime libmd, resolves every name to an address, and lays the program out for the 68000's memory map — code and data from address 0 upward. Out comes an ELF (rom.out), symbols and all. - Convert.
m68k-elf-objcopystrips the ELF wrapper and writes the raw bytes the console expects: a flat ROM image (rom.bin, sometimes.md). SGDK also fixes up the header — length and checksum — at this point.
Keep both outputs. The rom.out ELF is your
debugging artefact — BlastEm and Genesis Plus GX can load
its symbols so a debugger shows function names. The
rom.bin is your shipping artefact — the exact
bytes that go on a cartridge or flashcart.
The ROM header — the cartridge's ID card
A Genesis ROM begins with the 68000
vector table
(the first 256 bytes), and immediately after it, at offset
0x100, sits a 256-byte header: the
cartridge's ID card. This is not a homebrew invention — every retail
cartridge carries the same structure, and TMSS-equipped consoles
actually read the first bytes of it at boot. It is plain, mostly
human-readable data: the console signature "SEGA MEGA DRIVE", the
title in two languages, a serial number, a checksum, which
peripherals the game supports, the ROM and RAM address ranges, and
the region codes.
Everything in it is big-endian — the 68000 stores the most-significant byte first, the opposite of your x86 laptop. The lab below is a synthetic-but-format-correct header for a homebrew ROM, generated by this page. Click each field group to highlight its bytes (and their ASCII) and read what they mean. SGDK fills most of these in for you from a small config, but knowing the layout is what lets you set a title, declare 6-button support, or add save RAM.
The ROM header is the machine-readable page of a passport: a fixed layout of fields — name, place, dates, a checksum line — that any border booth can parse without knowing the traveller. The console at power-on is that booth: it glances at "SEGA", checks the region is one it serves, and waves the cartridge through to the reset vector. The rest of the fields are for tools and humans, not the boot ROM — but filling them in properly is what makes your ROM feel like a real cartridge.
- The pipeline is compile → resource-compile → link → convert: gcc + rescomp make objects, ld builds an ELF against libmd, objcopy emits the raw ROM.
rom.out(ELF) is for debugging with symbols;rom.binis the shipping cartridge image.- A ROM starts with the 68000 vector table, then a 256-byte header at 0x100: signature, titles, serial, checksum, I/O, ROM/RAM ranges, region.
- Everything is big-endian; retail games use the identical header, and SGDK fills most of it in for you.
Tiles & the VDP from C
The VDP is the chip you'll spend the most time with, and it thinks entirely in tiles. A tile is an 8×8 pixel block stored at 4 bits per pixel — so each pixel is a number from 0–15, an index into a 16-colour palette line. The VDP holds four palette lines (64 colours on screen), and every tile picks one of them. Colour 0 in a line means "transparent" for sprites and the front plane. That's the whole colour model: 512 possible colours, 64 visible, chosen 16 at a time.
Driving it from SGDK
You rarely poke the VDP's registers by hand — SGDK wraps the common jobs. The pattern is: load a palette, upload a tileset to VRAM, then write tile indices into a plane's tilemap.
// 'logo' is generated by rescomp from an image listed in resources.res PAL_setPalette(PAL1, logo.palette->data, DMA); // 16 colours → palette line 1 VDP_drawImageEx(BG_A, &logo, TILE_ATTR_FULL(PAL1, 0, 0, 0, TILE_USER_INDEX), 5, 3, FALSE, TRUE); // place it on plane A at cell (5,3) // scroll plane B slowly for a parallax backdrop VDP_setHorizontalScroll(BG_B, offset++);
A tile attribute — the value stored per cell in a
tilemap — packs four things into 16 bits: the tile's index in VRAM,
which of the four palette lines to use, horizontal and vertical
flip flags, and a priority bit that lifts the tile in front of
higher planes. TILE_ATTR_FULL just builds that word for
you. Scrolling is nearly free: the VDP reads scroll offsets from its
own memory every frame, and it supports whole-plane, per-8-pixel-cell
or even per-scanline horizontal scroll — the last
of which, combined with the
HBlank interrupt,
is how the era's water ripples and gradient skies were made.
A plane is a mosaic laid on a grid, but the clever part is that the box of tiles is small and swappable. You keep only the tiles currently on screen in VRAM; as the level scrolls, you quietly replace tiles at the edges just before they're needed. The picture can be enormous while the tile box stays tiny — the trick behind every side-scroller the console ever ran.
- The VDP is a tile engine: 8×8 4bpp tiles in VRAM, coloured by one of four 16-colour palette lines (64 colours from a palette of 512).
- It composites planes A and B, an optional fixed Window, and sprites — all from the same tiles.
- A tilemap cell is a 16-bit attribute: tile index + palette line + H/V flip + priority; SGDK's
TILE_ATTRmacros build it. - Scrolling is cheap and can be per-scanline; with the HBlank interrupt that yields the classic mid-screen raster effects.
Sprites (the SGDK sprite engine)
Planes are for backgrounds; sprites are for everything that moves independently — the player, enemies, bullets, the cursor. A VDP sprite is a small rectangle of tiles (up to 4×4, i.e. 32×32 pixels) that the hardware draws at any pixel position, over the planes, with its own palette line, flip and priority. The VDP keeps a sprite attribute table in VRAM — a linked list of up to 64 sprites — and composites them every frame.
The limits are real and worth memorising, because they shaped how every Genesis game looks:
Overrun the per-scanline budget and the VDP simply stops drawing the lowest-priority sprites on that line — the notorious sprite flicker. Games hid it by cycling sprite priority each frame so the dropout moved around and read as shimmer rather than disappearance.
Let SGDK do the bookkeeping
Managing the sprite list, uploading the right animation frame's
tiles to VRAM at the right moment, and cycling priority by hand is a
lot of plumbing. SGDK's sprite engine does it for
you: you define a SpriteDefinition (frames, animations,
collision boxes) as a resource, then create and move sprites with a
few calls.
// 'imgHero' comes from a SPRITE line in resources.res SPR_init(); // start the sprite engine Sprite* hero = SPR_addSprite(&imgHero, 120, 80, TILE_ATTR(PAL2, 0, FALSE, FALSE)); while(1) { SPR_setPosition(hero, x, y); // move it this frame SPR_setAnim(hero, running ? ANIM_RUN : ANIM_IDLE); SPR_update(); // rebuild the sprite list… SYS_doVBlankProcess(); // …uploaded to VRAM at vblank }
The engine streams only the current frame's tiles into VRAM (so a big animation doesn't need all its frames resident at once), builds the hardware sprite list from your logical sprites, and hands it to the VDP during vblank. You think in "the hero, at (x, y), running"; it thinks in tiles and list entries.
Sprites are actors on a stage only so deep: at most 64 can be on at once, and only so many can stand shoulder-to-shoulder on any one line before the ones at the back get shoved off. A good director — SGDK's engine, and your design — keeps the cast within budget and rotates who stands where, so the audience never notices anyone was ever missing.
- A sprite is up to 32×32 pixels of tiles at any pixel position, with its own palette line, flip and priority; up to 64 per frame.
- Hard limits — 20 sprites and 320 sprite-pixels per scanline (H40) — cause flicker when exceeded; priority cycling hides it.
- SGDK's sprite engine manages the sprite list, VRAM streaming and animation from a resource-defined
SpriteDefinition. - You move and animate logical sprites;
SPR_update()+ vblank push the hardware list for you.
Sound: the XGM driver (FM music + PSG SFX on the Z80)
The Genesis makes sound with two chips, orchestrated by a third processor. The YM2612 is an FM synthesiser — six channels, each built from four operators wired together by one of eight algorithms — responsible for the console's punchy basses, brass and bells. Channel 6 can instead be switched to an 8-bit DAC to play digital drum and voice samples. Alongside it, the PSG (an SN76489) adds three square-wave tones and a noise channel — the chirps, hats and blips. Both are physically closer to the Z80 coprocessor than to the 68000.
That geography is the whole reason sound is easy in SGDK. Feeding FM registers in real time from the 68000 would eat your frame budget. Instead, SGDK ships the XGM driver: a small program that runs on the Z80, reading a pre-logged stream of register writes and pushing them to the YM2612 and PSG on its own schedule. Your 68000 code just says "play this track" or "play this effect" and gets back to the game.
In practice
// 'music' (an XGM track) and 'sfxJump' (a PCM/effect) come from resources.res XGM_startPlay(music); // start the FM/PSG music, looping ... if (JOY_readJoypad(JOY_1) & BUTTON_A) XGM_startPlayPCM(sfxJump, 1, SOUND_PCM_CH2); // fire a sound effect
You list music and effects in resources.res;
rescomp converts a .vgm or tracker export
into the driver's compact format at build time. From C it's a
handful of XGM_* calls. (There is a newer XGM2 driver in
recent SGDK with more PCM channels and lower CPU cost; the shape of
the API is the same.)
You are the bandleader, but you don't play the instruments. You
hand the band (the Z80 running XGM) a setlist and the sheet music,
and they perform it on the YM2612 and PSG while you run the rest of
the show. Calling XGM_startPlay is nodding to the
drummer to start the next number — one gesture, and you're free to
do your own job.
- Two chips: the YM2612 (6-channel FM, ch6 doubles as an 8-bit PCM DAC) and the SN76489 PSG (3 tones + noise).
- The Z80 coprocessor drives them; SGDK's XGM driver runs there and streams register writes so the 68000 stays free.
- From C you list tracks/effects in
resources.resand trigger them with a fewXGM_*calls. - Music is typically FM with sampled drums; sound effects are often PSG or short PCM — mixed without hurting your frame budget.
Reading the controller
Controller input on the Genesis is refreshingly honest. There are no
events, no callbacks, no queues. Once per frame SGDK reads the pads
for you; you ask JOY_readJoypad(JOY_1)
for a bitmask of what's currently held on controller 1, and test
bits with a bitwise AND. Under the hood, that mask comes from the
controller port at 0xA10003 in the
68000's address space.
The original 3-button pad has a D-pad and four buttons: A B C and START. Reading it is almost trivial — but the port only exposes six lines at once, so the console reads the pad twice per frame, toggling a select line (TH) to get the D-pad on one read and the buttons on the other.
The 6-button pad adds X Y Z and MODE. It's backward-compatible: read it the simple way and you get A B C START as before. To reach the extra buttons, software toggles the select line several times in quick succession and the pad reveals additional banks — a little handshake SGDK performs for you. Your code just sees more bits set in the same mask. (There's a historical gotcha: MODE was designed partly so a game could detect the 6-button pad, since some older titles mis-read it; if you support 6-button, test it deliberately.)
Each button owns one bit, with SGDK names like
BUTTON_A (0x0040), BUTTON_START (0x0080),
BUTTON_X (0x0400) and BUTTON_MODE
(0x0800). The lab shows the live mask: drive the drawn pad with your
keyboard (or plug in any USB gamepad — the browser's Gamepad API
stands in for the console's port) and watch bits flip. Toggle
between 3- and 6-button to see the extra bank light up.
BUTTON_* constants.
Desktop input is a doorbell: events ring whenever they please and you answer. Console input is a photograph: once per frame you take one picture of every button, and for the next 16.7 milliseconds you reason about that frozen picture. Comparing this photo with the previous one is how "held" becomes "just pressed" — the edge is literally the difference between two snapshots, and using held where you meant the edge is the classic menu-flickers-past-five-options bug.
- Read the pad once per frame;
JOY_readJoypad(JOY_1)returns a bitmask you test with bitwise AND. The hardware port is0xA10003. - 3-button pad: UP DOWN LEFT RIGHT A B C START. 6-button adds X Y Z MODE via a select-line handshake SGDK performs for you.
- Each button is one bit (
BUTTON_A= 0x0040,BUTTON_MODE= 0x0800, …); the extra bank only appears on a 6-button read. - Held = level (continuous), the change between frames = edge (discrete). Input is stage one of Module 10's loop.
The 60 Hz game loop
Every module so far has been a part; this is the whole. A Genesis game — yours, or any retail cartridge ever pressed — is one loop:
while (1) { u16 pad = JOY_readJoypad(JOY_1); // 1 · snapshot the player's hands update_world(pad); // 2 · physics, AI, rules — pure computation draw_world(); // 3 · queue VDP writes & sprite updates SYS_doVBlankProcess(); // 4 · block until vblank, then flush to VRAM }
Step 4 is the one with teeth. The VDP scans a full frame to the TV
every 16.7 ms (NTSC 60 Hz; 20 ms on
PAL 50 Hz), and SYS_doVBlankProcess() blocks until
the
VBlank interrupt
arrives — and only during that brief blanking window is it safe to
push a lot of data into VRAM (draw outside it and you fight the beam
for the bus). So the loop runs at exactly 60 Hz — if
steps 1–3 fit inside the budget. Spend 10 ms computing, wait
6.7: sixty perfect frames. Spend 17 ms — one millisecond over —
and you don't get 59 frames: you miss the vblank,
wait for the next one, and drop to 30 Hz. The budget
isn't a speed limit you can nudge past; it's a train that leaves the
station with or without you.
Two facts make console timing a gift instead of a terror. First, the VDP double-buffers for you conceptually: you build the next frame's changes during the visible portion and commit them in the vblank window, so the screen never shows a half-updated frame. Second, exploit the machine's determinism. There is no OS, no other process, no thermal throttling, no surprise background update: the same code path costs the same 68000 cycles every single time. If your frame fits today, it fits forever. This is why 1990 games could promise a rock-solid 60 and keep the promise.
A game loop is a live TV broadcast: a new frame goes to air every 16.7 ms whether you're ready or not. A studio show rehearses until every segment fits its slot exactly — that's the determinism. Miss your slot and you don't air 4% late; you wait for the next slot while the audience watches a repeat. That's the drop to 30 Hz, and viewers feel it instantly.
The lab makes the budget physical. It's a tiny playable game — move the paddle with ←/→, A/D or the mouse — whose loop presents one frame per vblank, exactly like the code above. The slider adds simulated work to steps 2–3. Push it past the 16.7 ms line and watch the game snap from silk to judder: same game, one millisecond too greedy.
- The loop is read → update → draw → vblank; the VDP's 60 Hz (NTSC) / 50 Hz (PAL) frame rate is the clock and the contract.
- Overrun the 16.7 ms budget by any amount and you wait a whole extra frame: rates quantise (60 → 30), they don't degrade smoothly.
- The vblank window is the safe time to push VRAM;
SYS_doVBlankProcess()is where SGDK commits your frame. - Bare metal is deterministic: a frame that fits once fits always. Budget once, trust it forever.
Running it
A game nobody can run is a diary. The final stretch establishes the fast development loop in BlastEm, walks the path to a real console through a flashcart, and points you at where to go once the course lets go of your hand.
Running in BlastEm
BlastEm is your development console. It opens your
rom.bin directly — drag the file onto it, or pass it on
the command line — and your program boots in a window a second after
make finishes. This tight loop is something 1990-era
licensed developers, burning EPROMs to test each build, would have
envied.
BlastEm is a good choice for two reasons. It is
accuracy-focused: its 68000, VDP and timing models
are careful enough that code which behaves in BlastEm almost always
behaves on hardware — the opposite of developing against a
forgiving emulator and discovering your timing was luck. And it has a
built-in debugger: a console where you set
breakpoints, step 68000 instructions, inspect registers and memory,
and watch VDP state. Point it at the rom.out ELF and it
can use your symbols, so you break on update_world
rather than a bare address.
- Debug with symbols. Load the ELF; the debugger shows function and variable names — the CPU course's Part III, now aimed at your code.
- Watch the VDP. BlastEm can show plane and VRAM state, so "why is my sprite invisible" becomes a thing you can see, not guess.
- Map a controller. Keyboard or any USB gamepad stands in for a Genesis pad — Module 09's lab is exactly what your ROM reads.
Keep a second emulator around for cross-checking: Genesis Plus GX is another accurate, widely used open-source core (you'll meet it as a RetroArch core and in standalone builds). Its Nuked-OPN2 FM model reproduces the YM2612 faithfully, which matters the moment your music relies on the chip's real quirks. If your ROM works in both BlastEm and Genesis Plus GX, it is very likely correct.
BlastEm is the rehearsal studio: mirrors on every wall (the debugger), stop-and-repeat any scene (savestates), no audience. Real hardware (Module 12) is opening night — same script, but now the CRT is real and the timing is merciless. Rehearse in the studio daily; go to the theatre to be sure the show works under stage lights. An accurate studio is what makes opening night boring in the best way.
- BlastEm opens
rom.bindirectly — the edit-compile-test loop is seconds long. - Its accuracy means "works here" strongly predicts "works on hardware"; its debugger + your ELF symbols give real breakpoints.
- Cross-check in Genesis Plus GX (with Nuked-OPN2 for faithful FM) to catch anything one emulator forgives.
- Develop against accuracy, not leniency — it saves you the worst kind of bug, the one only real hardware shows.
On real hardware (Mega EverDrive & other flashcarts)
Sooner or later you'll want the real thing: your tiles on a real VDP, through a real CRT. Because the Genesis never locked its cartridge slot, getting there is delightfully simple — no exploits, no soldering for the common path. You need a flashcart: a cartridge with an SD-card slot and rewritable memory that presents your ROM to the console as if it were a mask ROM.
BlastEm / Genesis Plus GX
Open rom.bin directly. Instant, free, with a debugger. Where all development happens regardless of what you own (Module 11).
Mega EverDrive
Krikzz's flashcart line (Mega EverDrive Pro / X7 / X3). Copy rom.bin to the SD card, insert the cart, power on, pick your ROM from its menu. The de-facto homebrew testing cartridge.
Other flashcarts
The Mega SD (Terraonion) and various community/clone carts do the same job — SD in, ROM on the console. Any reputable Genesis flashcart runs your .bin unchanged.
Burn an EPROM
The old-school route: flash your ROM to an EPROM, wire it into a donor cartridge board. Educational and permanent, but a flashcart is far easier for day-to-day testing.
The small gotchas emulators hide
- Region & refresh. A Japanese or US console runs NTSC at 60 Hz; a European one runs PAL at 50 Hz with 240 visible lines instead of 224. If you assumed 60 Hz timing, a PAL machine runs your game ~17% slower. SGDK exposes the detected region; budget and pace against it, and test on the hardware you care about.
- TMSS. Later consoles check the header's "SEGA" signature and require the ROM to write "SEGA" to a register at boot. SGDK's startup does both, so a stock SGDK ROM passes — but it's the reason you can't just dump arbitrary bytes at address 0 and expect a boot.
- Real timing bites. Effects that "worked" in a lenient emulator — squeezing too much into vblank, racing the beam — can glitch on hardware. This is exactly why Module 11 argued for an accurate emulator: the gap between studio and stage should be as small as you can make it.
The bright line, one last time: put your own software on
the flashcart. The scene runs on people sharing homebrew ROMs they
wrote; it does not need — and this course does not touch — copying
commercial cartridges. A flashcart loaded with your own
rom.bin is the clean, intended use.
A mask-ROM cartridge is a pressed vinyl record: made once at a factory, unchangeable. A flashcart is a rewritable disc in the same sleeve — the turntable (the console) can't tell the difference and plays it just the same, but you can burn a new track any time your build changes. That's the whole magic: the console asks no questions about who cut the record.
- A flashcart (Mega EverDrive and friends) loads your
rom.binfrom SD onto a real Genesis — no exploit, no signing. - Region matters: NTSC 60 Hz / 224 lines vs PAL 50 Hz / 240 lines changes your timing and layout — detect and adapt.
- TMSS is satisfied automatically by SGDK's startup (the "SEGA" header + register write).
- Test on real hardware to catch timing effects an emulator may forgive; ship only your own ROMs.
Where to go next
Here is the entire course in one chain. Trace it once with your finger; every link is now something you've either studied or built:
When the first module of any course in this collection began, the Genesis was a sealed appliance. Somewhere in the middle it became a diagram. If this course did its job, it is now something better: a target. The 68000 whose registers you studied executes your opcodes. The VDP planes you toggled in a lab scroll your tiles. The YM2612 plays your music; the vblank you learned to fear and love paces your loop. There is no layer of the machine left that is someone else's business.
Where to go from here
- Mine the samples. SGDK's
sample/tree has a working project for nearly every subsystem — the fastest way to learn an API is to build its example and start bending it. - Read the source. SGDK is open;
libmd's VDP, sprite and sound code is a readable master-class in driving the hardware you studied. - Use BlastEm as a microscope. Its debugger and VDP views (Module 11) are as good at inspecting your game as anyone else's.
- Join the room. The SGDK community (its GitHub, the Spritesmind forums, and active Discords) has answered every beginner question many times over. Ask well: post code, name your SGDK version.
- Revisit the other courses as manuals. They were written as teardowns — 68000, VDP, audio, Z80 — but each reads differently now that the hardware is yours to command.
A good first project, if you want one: a one-screen arcade game —
paddle, ball, score, one FM music track, one PSG sound effect. It
touches every module of this course, fits comfortably in the frame
budget, packs into a single rom.bin, and it is exactly
the kind of thing the scene has cherished for decades: proof that a
1988 machine still says yes to anyone who learns to ask.
- You can set up SGDK on Windows, macOS or Linux, and explain every piece of the toolchain.
- You can read a Genesis ROM header byte by byte, and narrate the pipeline that produced it.
- You can put text, tiles, sprites and FM/PSG sound on screen, read the pad, and pace a 60 Hz loop.
- You can run your ROM in BlastEm and on a real console via a flashcart. The other courses taught you the machine. This one made it yours.