An interactive course · SGDK & m68k-elf-gcc

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.

Part I

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.

Modules 01–03Difficulty BeginnerPrerequisite some C helps
Module 01 · Part I

What homebrew is (and isn't)

GoalKnow exactly what "homebrew" means, why writing your own Genesis software is a legitimate craft distinct from piracy, and what the scene has built.

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.)

Analogy · Cooking in someone else's kitchen

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.

Licensed development (1989) Sega SDK & docs under NDA dev cartridge HW leased, not sold Homebrew (today) SGDK + m68k-elf-gcc community docs your C code same on both paths 68000 machine code the CPU can't tell the difference 🕹️
Two roads, one destination. A licensed studio compiled C (or hand-wrote 68000 assembly) against Sega's SDK; you'll compile C against SGDK with a free compiler. Both roads end in Motorola 68000 machine code, and the CPU executes yours exactly as willingly as it executed a retail cartridge. The hardware has no opinion about who wrote the program.

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.

Key takeaways
  • 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.
Module 02 · Part I

The toolchain: SGDK & m68k-elf-gcc

GoalUnderstand what a cross-compiler is, what each piece of SGDK does, and why code built on your x86/ARM laptop runs on a Motorola 68000 console.

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.

Analogy · The translator who never travels

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:

SGDK
The umbrella project: a C library (its runtime is called libmd), a set of asset tools, sample projects, and the makefiles that tie it all together. Everything below ships inside it.
m68k-elf-gcc
The cross-compiler: GCC configured to target 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).
rescomp
SGDK's resource compiler. It reads a .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.
libmd
The hardware library — Module 06's and beyond's subject. It wraps the VDP, the controllers, DMA, the sound driver and more in a sane C API, so you don't poke registers by hand.
the XGM driver
A small sound driver that runs on the Z80 coprocessor, playing FM music and PSG sound effects while the 68000 gets on with the game (Module 08).
sample projects
A tree of small, working examples — sprites, scrolling, sound, input — that double as templates. Your first build in Module 04 starts from one.

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.

HOST — your computer (x86-64 / ARM) main.c your source m68k-elf-gcc runs here… also here: make, rescomp, objcopy, objdump, your editor, BlastEm rom.bin (68000) TARGET — the Genesis (68000) 68000 @ 7.67 MHz …code runs here 64 KB RAM + VDP where the ROM maps in no OS, no loader of its own — your ROM owns the whole machine from the reset vector onward
Host and target. Every tool runs on your computer; only the finished ROM image crosses to the console. An emulator like BlastEm blurs the line delightfully — it makes your host pretend to be the target, which is why the edit-compile-test loop in Module 11 can be seconds long.

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.

Key takeaways
  • A cross-compiler runs on your machine but emits code for another — here, m68k-elf-gcc emitting 68000 machine code.
  • SGDK bundles the compiler, binutils, the libmd runtime, 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.
Module 03 · Part I

Setup — Windows, macOS or Linux

GoalInstall SGDK and the m68k toolchain on your OS, verify the install by building a real sample, and have BlastEm ready to run what you build.

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 GDK to that folder (GDK=C:\sgdk).
  • Build from a project folder using the make that ships inside SGDK.
cmd / PowerShell
:: 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:

Terminal
# 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:

shell
# 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:

any OS
$ 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.

Key takeaways
  • 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 GDK variable (Windows) or a mounted volume (Docker), then just run make.
  • Build a bundled sample to a rom.bin before writing your own — it proves the whole chain.
  • Install BlastEm as your test console; SGDK's README is the authority when details drift.
Part II

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.

Modules 04–10Difficulty Beginner → IntermediateLabs 5 interactive · 1 playable
Module 04 · Part II

Hello, world

GoalRead the canonical SGDK hello-world line by line, and watch text appear on screen the only way the Genesis knows how — as tiles on a VDP plane.

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:

src/main.c
#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.

Analogy · A wall of pigeonholes

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.

First-boot lab — the hello-world power-on sequenceCanvas · simulation
state powered off frames
A dramatisation of the boot order: a generic boot wordmark (not Sega's logo) → VDP init → VDP_drawText laying tiles into plane A → the SYS_doVBlankProcess loop. Each glyph occupies one highlighted 8×8 tile cell.
Key takeaways
  • 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_drawText places 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.
Module 05 · Part II

The build pipeline & the ROM header

GoalFollow your source through compile, resource-compile, link and convert; then open the cartridge ROM header — the same one every retail game carries — field by field.

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.

main.c · .res
your source + resources
*.o
m68k-elf-gcc + rescomp
rom.out
m68k-elf-ld: + libmd, an ELF
rom.bin
objcopy: raw big-endian ROM
  • Compile. m68k-elf-gcc -c turns each .c file into a .o object of 68000 machine code with unresolved references ("I call something named VDP_drawText, wherever that is").
  • Resource-compile. rescomp reads your resources.res and 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-ld fuses 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-objcopy strips 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.

Build-pipeline lab — watch make workCanvas · animation
newest artefact main.c size 1.4 KB
Sizes are illustrative. Notice most of the final ROM is assets — tiles, palettes and music that rescomp packed in — not your handful of C lines.

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.

ROM-header lab — inspect a real header, byte by byteinteractive
A synthetic but format-correct header at file offset 0x100. All multi-byte values are big-endian — your x86 laptop would read every number byte-reversed, a classic 68000 porting gotcha.
Analogy · The passport page

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.

Key takeaways
  • 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.bin is 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.
Module 06 · Part II

Tiles & the VDP from C

GoalUnderstand the VDP's tile-and-plane model — 8×8 4bpp tiles, 16-colour palette lines, planes A and B and the Window — and drive it from SGDK.

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.

one 8×8 tile · 4bpp (indices 0–15) palette line · 16 colours (9-bit BGR) index 0 = transparent · 3 bits per R/G/B channel arranged into a plane's tilemap → plane A / B: 64×32 (or 64×64) tile cells what the VDP composites · plane B — far background (scrolls) · plane A — near background (scrolls) · Window — a fixed, non-scrolling region of A · sprites — up to 64 moving tile groups resolution 320×224 (H40) or 256×224 (H32)
Everything is tiles. The VDP composites two scrolling planes (A and B), an optional fixed Window carved out of A, and sprites — all built from the same 8×8 4bpp tiles in VRAM, coloured through the palette lines. Screen resolution is 320×224 in H40 mode (or 256×224 in H32); 224 lines on NTSC, 240 on PAL.

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.

tiles from C
// '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.

Analogy · A mosaic with a swappable tile box

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.

Key takeaways
  • 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_ATTR macros build it.
  • Scrolling is cheap and can be per-scanline; with the HBlank interrupt that yields the classic mid-screen raster effects.
Module 07 · Part II

Sprites (the SGDK sprite engine)

GoalKnow the VDP's sprite hardware and its limits, and let SGDK's sprite engine manage animation, the sprite list and VRAM for you.

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:

Sprites total
64
per frame (H40 mode)
Per scanline
20 / 16
H40 / H32 — excess dropped
Pixels / line
320
sprite pixels per scanline (H40)
Max size
32×32
4×4 tiles per sprite

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.

sprites from C
// '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.

Analogy · Actors on a shallow stage

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.

Key takeaways
  • 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.
Module 08 · Part II

Sound: the XGM driver (FM music + PSG SFX on the Z80)

GoalMeet the two sound chips and the Z80 that drives them, and use SGDK's XGM driver to play FM music and PSG effects without stealing time from your game.

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.

68000 · your game XGM_startPlay(track) Z80 · XGM driver 8 KB RAM · streams register writes on its own clock YM2612 (FM) 6 channels · 4 operators · ch6 DAC SN76489 (PSG) 3 square tones + 1 noise · 4-bit volume
The sound path. Your 68000 code issues one command; the XGM driver on the Z80 does the per-tick work of feeding the two chips. Music (FM, often with sampled drums on channel 6's DAC) and sound effects (typically PSG) mix without stealing measurable time from your game loop.

In practice

sound from C
// '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.)

Analogy · Handing the band a setlist

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.

Key takeaways
  • 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.res and trigger them with a few XGM_* calls.
  • Music is typically FM with sampled drums; sound effects are often PSG or short PCM — mixed without hurting your frame budget.
Module 09 · Part II

Reading the controller

GoalMaster the joypad: the scan-per-frame model, the button bitmask, the 3-button vs 6-button pad, and edge-versus-level reads.

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.

Controller lab — the live JOY_readJoypad maskCanvas · interactive
keys · ←↑↓→D-pad AA SB DC START ZX XY CZ VMODE
JOY_readJoypad(JOY_1) 0x000 binary 00000000
0 (nothing held)
X, Y, Z and MODE only respond in 6-button mode — exactly like the real handshake. A USB gamepad works too (press a button to wake the Gamepad API). The bit values shown are SGDK's real BUTTON_* constants.
Analogy · The photograph, not the doorbell

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.

Key takeaways
  • Read the pad once per frame; JOY_readJoypad(JOY_1) returns a bitmask you test with bitwise AND. The hardware port is 0xA10003.
  • 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.
Module 10 · Part II

The 60 Hz game loop

GoalInternalise the read → update → draw → vblank loop, the 16.7-millisecond budget it must fit in, and what missing that budget looks and feels like.

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:

the canonical 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.

Analogy · Live television

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.

Frame-budget lab — a playable loop you can overloadCanvas · playable
presenting 60 fps missed vblanks 0 score 0
←/→, A/D or mouse to move. The meter along the bottom is your frame time against the 16.7 ms NTSC budget: green fits, red misses — and the instant it crosses the line, 60 Hz becomes 30. There is no 45.
Key takeaways
  • 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.
Part III

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.

Modules 11–13Difficulty Easy readingMood ship it
Module 11 · Part III

Running in BlastEm

GoalMake an emulator your development console: the seconds-long build-run loop, symbol debugging, and why an accuracy-focused emulator is the right rehearsal room.

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.

Analogy · The dress rehearsal and the opening night

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.

Key takeaways
  • BlastEm opens rom.bin directly — 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.
Module 12 · Part III

On real hardware (Mega EverDrive & other flashcarts)

GoalGet your ROM onto a real Genesis through a flashcart, and understand the small real-world gotchas — region, 50/60 Hz, and TMSS — that emulators hide.

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.

no console needed
BlastEm / Genesis Plus GX

Open rom.bin directly. Instant, free, with a debugger. Where all development happens regardless of what you own (Module 11).

needs · just your PC
the standard kit
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.

needs · console + Mega EverDrive + SD card
alternatives
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.

needs · console + a flashcart + SD card
the deep end
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.

needs · an EPROM programmer & a donor board

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.

Analogy · The rewritable disc

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.

Key takeaways
  • A flashcart (Mega EverDrive and friends) loads your rom.bin from 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.
Module 13 · Part III

Where to go next

GoalClose the collection's loop: see the whole path from your keyboard to the scanline, collect the best next steps, and go build something.

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:

main.c · .res
your idea, in C (04)
m68k-elf-gcc
cross-compiled + SGDK (02–05)
rom.bin
a real cartridge image (05)
BlastEm / flashcart
loaded & run (11–12)
the machine
68000 · VDP · Z80 · your loop (06–10)

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.

Course complete ✦
  • 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.

Part of the Inside the Sega Genesis collection. Every screen, controller and build on this page is a synthetic browser simulation; the page ships no Sega code, tools or assets, the boot screen is a generic stand-in (never Sega's logo or jingle), and neither the page nor the toolchain it teaches contains any Sega material. Toolchain facts follow the public SGDK project and the open-source emulators SGDK, BlastEm, Genesis Plus GX and Nuked-OPN2; installer and product details drift over time, so where this page and SGDK's README disagree, trust the README. "Sega", "Genesis" and "Mega Drive" are trademarks of Sega; this is an independent, non-commercial educational project with no affiliation. Homebrew described here means running your own software on hardware you own — this page neither contains nor endorses tools for copying games.

Source of truth · github.com/Stephane-Dallongeville/SGDK · retrodev.com/blastem · github.com/ekeeke/Genesis-Plus-GX