GERLA.CC

tutorial · Mega Drive

Assembler and C, why both?

The Mega Drive game is written partly in assembler and partly in C. Why two languages for one game? Because they're good at different things, and used together they do a better job. Let's see the difference with a few code examples: the pictures and the green captions are enough to follow along.

01 · the CPU

The CPU understands only one language

Inside the Mega Drive there's a CPU, the Motorola 68000: it's the processor that runs the game. A CPU understands only its machine language, made of elementary instructions: move a value, add it, compare it, jump somewhere else. Assembler is the readable way to write those instructions, one by one. For example, putting a color into the palette looks like this.

assembler · the CPU's language

; scrivi il colore bianco nella palette move.w #$0EEE, d0 ; d0 = bianco move.w d0, (a1) ; scrivilo nel colore n.0

Two instructions for a single color. Precise, but writing a whole game at this level would be slow and error-prone. A more convenient layer is needed.

02 · the compiler

C and the compiler

Here's the convenient layer: C. C is a language closer to how we think, where things are said short and clear. But the CPU doesn't run C: it needs a compiler that turns C into machine instructions. The path is this:

C codehow it's written
→
compilertranslates
→
machine codethe instructions
→
68000 CPUruns it

On the left the C as it's written, on the right the machine code the compiler produces.

C · how it's written

/* riempi di bianco tutti e 16 i colori */ for (int i = 0; i < 16; i++) { palette[i] = 0x0EEE; }

assembler · what the compiler generates

moveq #0, d0 ; i = 0 .loop: move.w #$0EEE, (a0) ; palette[i] = bianco addq.w #2, a0 ; punta al colore dopo addq.w #1, d0 ; i = i + 1 cmp.w #16, d0 ; i < 16 ? blt .loop ; si': ripeti

Three lines of C become six machine instructions, and the compiler writes them, without mistakes. That's why most of the game is in C: shorter, clearer, faster to fix.

03 · the question

So why not everything in C?

If the compiler is so good, why not write everything in C and drop the assembler? Because there are three points where the compiler isn't enough and assembler must be written by hand. Let's look at them.

03a · boot

Boot, before C can run

At power-on the CPU starts in a raw state: memory isn't set up and, above all, there's no stack yet, the memory area C uses for function calls and local variables. Without a stack, C can't run. So the first bit of code, in assembler, prepares the ground and then hands over to C. By convention this file is called crt0. The boot sequence:

POWER ON
↓
the CPU reads the vector table
top of the ROM
↓ jumps to _start
assembler · crt0
sets up the stack, disables interrupts
↓ calls main()
C · main()
init and game loop: the game starts

crt0.s · assembler

_start: move.w #$2700, sr ; disabilita le interruzioni lea stack_top, sp ; imposta lo stack pointer jsr main ; passa il controllo al C .stop: bra .stop ; se main torna, resta qui

main.c · C

int main(void) { /* da qui in poi lavoro comodo, in C: lo stack e' pronto grazie al crt0 */ init_game(); game_loop(); return 0; }

Assembler sets the stage; then C takes over. Without those few opening lines, C couldn't even start.

03b · the vectors

The vector table

The moment it powers on, the CPU reads a vector table at the top of the ROM: a list of addresses in a fixed order set by the hardware. Here's how the ROM is laid out, and which parts are assembler and which are C.

↑ the CPU starts at address 0, at the top
vector tableasm
crt0 · bootasm
game code (C → compiled)C
data: graphics, levels, music

assembler · the vector table

dc.l stack_top ; 0) indirizzo dello stack dc.l _start ; 1) da dove iniziare dc.l err, err ; errori di bus / indirizzo dc.l err, err ; istruzione illegale, ... ; (e cosi' via, in ordine fisso)

dc.l means "a 32-bit value goes here". Each line is an entry in the table. The hardware always reads it in this order: move the entries and boot crashes.

03c · speed

When speed matters

The compiler generates good code, but in some spots maximum speed is needed: for example copying a lot of data into VRAM (video memory) within one frame, or a stutter shows. In these critical spots hand-written assembler, picking the best instructions, beats the compiler. Just a few lines, but decisive for smoothness.

assembler · hand-written, for speed

; copia veloce verso la VRAM: ; 4 byte per istruzione, senza sprechi move.l (a0)+, (a1) ; blocco 1 move.l (a0)+, (a1) ; blocco 2 move.l (a0)+, (a1) ; blocco 3 move.l (a0)+, (a1) ; blocco 4

Here control must be total: the instructions are hand-picked to be the fastest. It's the one case where assembler beats the compiler, using a trick the compiler doesn't apply.

04 · the logic

Everything else: the logic, in C

Apart from those three points, all the game logic (character movement, jumps, enemies, collisions, score) is written in C. It's the part that changes most often, and in C it's more readable and quicker to change.

game.c · the character's jump

void aggiorna_giocatore(void) { if (premuto(FUOCO)) player.vy = -FORZA_SALTO; /* salta su */ player.vy += GRAVITA; /* accelera verso il basso */ player.y += player.vy; /* aggiorna la posizione */ if (player.y > PAVIMENTO) player.y = PAVIMENTO; /* fermati al suolo */ }

It reads almost like pseudocode: if fire is pressed, jump; gravity accelerates downward; update position; stop at the floor. In pure assembler it would be three times the lines and harder to change.

in short

Two tools, one job

assembler · the low level

- l'avvio e lo stack (crt0) - la tabella dei vettori - le routine critiche per la velocita' (es. VRAM)

C · the game logic

- movimento del personaggio - nemici, salti, collisioni - vite, punteggio, game over - tutto cio' che cambia spesso

Assembler and C aren't in competition: they split the work. Assembler handles boot and the low-level spots; C expresses the logic clearly. Together they're more efficient than either alone. On top of that, the same C code can also be compiled on the development machine: that way it's tested before going into the ROM.

← All tutorials Mega Drive walls overcome