How the GameCube thought.
We start from the very beginning — what a processor even is — assuming you know nothing about computer architecture. Once the vocabulary is solid, we meet Gekko, the modified IBM PowerPC at the console's heart, and finish inside Dolphin's just-in-time compiler. Fifteen modules, each with a live, clickable machine so you can watch every idea run, not just read about it. No game code ships with this page; every simulation is built in your browser.
How CPUs work
Instructions, registers, binary and floating point, pipelines, superscalar issue and caches — the universal vocabulary of processors, explained from zero.
Gekko
The chip itself: paired singles, quantised loads, the write-gather pipe, the locked cache, and the memory system it lives in.
Emulating in Dolphin
Interpreters vs JITs, the accuracy traps that make emulation hard, and the settings that map to it all.
How CPUs work
This first part is pure fundamentals — nothing about any specific console or chip yet. It's the toolkit every processor is built from, whether that's your phone, your laptop, or the chip we'll meet in Part II. We build the vocabulary one word at a time — instruction, register, pipeline, cache — and you can step and run each idea in a tiny simulated machine. If a term is ever used before it's explained, that's a bug; tell us. Everything in Parts II and III is just these six ideas wired together.
The fetch–decode–execute loop
Let's start with what a processor physically is. Strip away the marketing and a CPU is a machine that repeats one tiny ritual, billions of times per second, without ever getting bored. The ritual has three steps: fetch a number from memory, decode it — work out what command that number stands for — and execute it. Then do the next one. That's the whole job. Everything a computer has ever done — every game, every physics engine, every pixel — is that loop, running very fast.
Two words in that ritual need unpacking. Memory is
a long row of numbered pigeonholes, each holding one small number (a
byte).
The pigeonhole's position in the row is its
address — cell 0, cell 1, cell 2, and so on. And an
instruction is just one of those stored numbers
that the CPU has agreed to treat as a command. There is nothing
special about the number itself: 0x1E sitting in memory
might mean “load from cell 14” to the processor, or it
might just be the number 30 in someone's data. Position and
convention decide, nothing else. Programs and data live in the same
memory, side by side — this is the single most important idea in
all of computing, and (in Module 14) it's also what makes emulators
sweat.
So how does the CPU know which cell holds its next command? It keeps a bookmark: a small counter called the program counter (PC). Each trip round the loop it fetches the instruction the PC points at, then normally bumps the PC forward by one so the next trip fetches the next cell. The exception is a branch (a “jump”): an instruction whose entire job is to overwrite the PC with a new address, so execution leaps somewhere else. Branches are how programs make decisions and loops — and in Module 04 they'll turn out to be the pipeline's biggest headache.
Picture a cook working from a stack of numbered recipe cards, one short command per card: “fetch the flour”, “add two eggs”, “go back to card 3”. The cook keeps a finger on the current card — that finger is the program counter. Read a card, do what it says, slide the finger to the next card. The cook never sees the “whole recipe”; only ever one card at a time. A CPU is exactly this cook — just one who reads half a billion cards a second and never misreads one.
A worked example, by hand
Let's invent the smallest possible machine and run it on paper. Our
toy CPU has 16 memory cells (addresses 0–15), one working register
called A (a register is a scratch value inside the
CPU — Module 02 is all about them), and a PC. Each instruction is
one byte: the first hex digit says what to do — the
opcode
— and the second says which cell to do it to. Our whole
instruction set is four opcodes: 1n = load
cell n into A, 2n = add
cell n to A, 3n = store A into
cell n, 4n = jump to
cell n.
Now put this five-instruction program in cells 0–4, with data in
cells 12–14: 1E 2D 2C 3E 40. Read
it with the table above: load cell 14, add cell 13, add cell
12, store cell 14, jump to 0. Cell 13 holds 2 and cell 12
holds 1, so each trip round the loop the number in cell 14 grows by
3. That's it — a counter, the “hello world” of
hardware. The lab below is this exact machine. Press
Step and watch each fetch, decode and execute
happen with your own eyes; press Run and watch it
become a program.
- A CPU repeats one loop forever: fetch an instruction, decode it, execute it.
- Instructions are ordinary numbers in memory — programs and data share the same pigeonholes.
- The program counter is the bookmark; a branch is an instruction that rewrites it.
- An instruction's number splits into an opcode (what to do) and operands (what to do it to).
Registers, memory & the ISA
Our toy CPU had one working register, A. Real processors have a few dozen, and the reason is distance — literally. Memory is a huge structure physically far (in chip terms) from the circuits that do arithmetic, so every trip to it costs time. A register is a small storage slot built inside the CPU, right next to the arithmetic circuitry. It holds one value — 32 or 64 bits — and reading or writing it costs essentially nothing. Registers are the workbench; memory is the warehouse. There are only a handful of registers precisely because they're so close: you can't fit a warehouse on a workbench.
A carpenter doesn't walk to the warehouse for every screw. They fetch a tray of parts to the bench, work there — where everything is within arm's reach — and carry results back when done. A register is a spot on the bench; a load is the walk to the warehouse shelf; a store is the walk back. Good code, like a good carpenter, organises the work to stay at the bench as long as possible.
That analogy is baked into hardware as the load/store architecture: arithmetic instructions are only allowed to work register-to-register, and the only instructions that touch memory are explicit loads and stores. Want to add two numbers that live in memory? Load both into registers, add the registers, store the result back. Three-plus instructions instead of one — but each is simple, uniform, and fast, which (Module 04) is exactly what a pipeline wants. This philosophy has a name you've heard: RISC.
What “PowerPC” actually names
Here's a distinction the whole course leans on. An instruction set architecture (ISA) is a contract: the documented list of instructions, the registers, and exactly what each instruction must do. An implementation is a physical chip that honours the contract. PowerPC is an ISA — a RISC design from IBM, Motorola and Apple in the early '90s. The PowerPC 750 is one implementation of it (sold by Apple as the “G3”), and Gekko — our Part II star — is a 750 derivative. Any chip honouring the contract runs the same programs; how fast, with what pipeline and caches, is the implementation's own business. This split is also what makes emulation possible: Dolphin (Part III) implements the PowerPC contract in software.
The PowerPC contract that matters to us: 32
general-purpose registers (r0–r31,
32-bit, for integers and addresses), 32 floating-point
registers (f0–f31, 64-bit — remember that
width; Module 08 turns it into Gekko's party trick), and a few
special ones:
| Registers | Width | What they hold |
|---|---|---|
| r0 – r31 | 32-bit | General-purpose: integers, addresses, loop counters — the workbench. |
| f0 – f31 | 64-bit | Floating-point values (Module 03). On Gekko, each can also hold two 32-bit floats (Module 08). |
| PC | 32-bit | The bookmark from Module 01 (PowerPC manuals call it the “current instruction address”). |
| LR · CTR · CR | 32-bit | Link register (return address for function calls), count register (hardware loop counter), condition register (results of comparisons, read by branches). |
And here's what real PowerPC code looks like — the same “add two numbers from memory” job we described, each line glossed in plain words. Every instruction is exactly 4 bytes, a fixed size that makes fetching and decoding beautifully predictable:
lwz r3, 0(r10)lwz r4, 4(r10)add r5, r3, r4stw r5, 8(r10)- Registers are a few dozen ultra-fast slots inside the CPU; memory is the big, slow warehouse.
- In a load/store (RISC) machine, arithmetic is register-to-register; only loads and stores touch memory.
- An ISA is a contract; a chip is an implementation of it. PowerPC is the contract, the 750 and Gekko are implementations — and Dolphin is one in software.
- PowerPC gives you 32 general-purpose registers and 32 double-width floating-point registers.
Numbers in silicon
Everything a CPU touches is stored in bits —
switches that are either 0 or 1. A row of bits reads as a number in
binary, exactly like decimal but with columns
worth 1, 2, 4, 8, 16… instead of 1, 10, 100. So
1011 is 8 + 2 + 1 = 11. With
n bits you get 2n patterns: a byte (8 bits)
covers 0–255, and the 32-bit registers from Module 02 cover a bit
over four billion values. So far, so mechanical.
Negative numbers need a convention, and the universal one is
two's complement: keep counting past the top and
let the value wrap around, like a car's odometer rolling over —
except we relabel the top half of the patterns as the
negatives. In 8 bits, 11111111 is −1,
11111110 is −2, down to 10000000 = −128.
The lovely consequence: the same adder circuit adds signed and
unsigned numbers correctly without knowing which it's doing.
The price: a fixed window (−128…+127 for a byte), and arithmetic
that silently wraps when you leave it. Keep two's complement in
your pocket — Module 09 stores whole vertex coordinates this way.
The hard part: fractions
Whole numbers were easy. But games live on fractions — positions, angles, velocities. The trick every modern CPU uses is floating point, and it's precisely scientific notation in binary. In decimal, scientific notation writes 42,500 as 4.25 × 104 — a significand (the digits) and an exponent (where the point sits). Binary floating point does the same with 2 as the base. The IEEE-754 standard — honoured by PowerPC, by your PC, and (imperfectly, see Module 14) between them — packs a 32-bit single-precision float into three fields:
- 1 sign bit — 0 for positive, 1 for negative.
- 8 exponent bits — where the binary point sits. Stored with a bias of 127: the field holds the true exponent + 127.
- 23 mantissa bits — the significand's fraction digits. Because a normalised binary significand always starts “1.”, that leading 1 isn't stored — a free 24th bit of precision.
The decoded value is
(−1)sign × 1.mantissa × 2exponent−127.
A 64-bit double is the same idea with an 11-bit
exponent and 52-bit mantissa — the format PowerPC's
f0–f31 registers hold natively. Singles are
half the memory and (on most hardware) faster; doubles are far more
precise. Games overwhelmingly choose singles — which is exactly the
door Gekko's paired singles (Module 08) walk through.
Floating point is a ruler whose tick marks squeeze together near zero and spread apart as numbers grow. Between 1 and 2 there are about eight million representable singles; between 16,777,216 and 16,777,218 there are none in between — the ticks are now 2 apart. Any real value gets snapped to the nearest tick; that snap is rounding, and the tiny gap it introduces is rounding error. Almost every float you'll ever meet is a nearest-tick approximation, not the true value.
And here's the honest part most introductions skip. In decimal,
1/3 has no finite representation — 0.333… forever. In binary the
same fate hits some very innocent-looking decimals:
0.1 is 1/10, and since 10 has a factor of 5 (not a
power of two), 0.1 in binary is
0.000110011001100… — repeating forever. A 32-bit float must
cut it off after 24 significant bits, so what's actually stored is
0.100000001490116…. Close, but not 0.1. Every
language, every chip, same story. Special bit patterns round out
the format: two zeros (+0 and −0, the sign bit alone differing),
infinities, NaN
for invalid results like 0/0, and tiny
denormals
that fill the gap next to zero. File all of these away: the exact
bits of rounding and NaNs are precisely where emulators bleed
(Module 14).
Now go poke the format yourself. Every bit below is a button. Start from the presets — see how 1.0 is mostly zeros, why the biggest finite single is ~3.4 × 1038, what −0 and NaN look like — then click 0.1 and read the stored value's full decimal expansion. It isn't 0.1. It never was.
- Binary counts in powers of two; two's complement wraps the pattern space so the same adder handles negatives.
- A float is scientific notation in base 2: sign × 1.mantissa × 2exponent−127.
- Singles (32-bit) trade precision for size and speed; doubles (64-bit) do the reverse. Games live on singles.
- Most decimals — 0.1 included — cannot be stored exactly; every float is a rounded nearest-tick approximation.
The pipeline
In Module 01 our toy CPU finished one instruction completely before starting the next: fetch, decode, execute, done, repeat. Real hardware noticed something wasteful about that. While an instruction is executing, the fetch circuitry is sitting idle — so why not have it fetch the next instruction at the same time? Push the idea to its conclusion and you get the pipeline: chop instruction processing into stages, and keep every stage busy with a different instruction at once.
Washing a load takes an hour, drying takes an hour, folding takes an hour. Doing four loads strictly one-after-another takes twelve hours. But while load 1 is in the dryer, the washer is free — so start load 2. Overlap everything and four loads take six hours, and a new finished load emerges every hour once the pipeline fills. Nothing got faster — the dryer still takes an hour — but the throughput tripled. That's a pipeline: same work, staggered, so all the machines run at once.
The classic textbook pipeline has five stages, and the labels are worth memorising because every architecture book uses them: IF (instruction fetch), ID (decode and read registers), EX (execute in the ALU), MEM (touch memory, for loads and stores), WB (write the result back to a register). One instruction still takes five clock ticks to travel through — its latency — but a full pipeline completes one instruction every tick. Architects measure this as CPI: cycles per instruction. The dream is 1.0. (Real chips from the 750's era ran short pipelines of about four stages; modern desktop chips run fifteen-plus. Same idea, more stages.)
The dream dies two ways. First, a data hazard: if instruction 2 needs the result of instruction 1, but instruction 1 hasn't reached write-back yet, instruction 2 must wait. The pipeline inserts a do-nothing slot — a bubble — and the CPI creeps up. (Hardware shortcuts called forwarding paths hand results backwards between stages to shrink these waits.) Second, and worse, a control hazard: a branch. When a branch enters the pipe, the fetch stage faces an impossible question: which instruction comes next? The answer isn't known until the branch executes, several stages later — but fetch can't just stop and wait every time.
So the hardware gambles. It predicts which way the branch will go — even a simple “assume the loop repeats” heuristic is right most of the time, and real predictors track each branch's recent history — and keeps fetching along the guessed path. Guess right: no time lost at all. Guess wrong: everything fetched after the branch is wrong-path work and must be thrown away. That's a flush — the pipeline's contents behind the branch turn into bubbles, and fetch restarts from the correct address. The deeper the pipeline, the more work a flush wastes. This is why branch-heavy code runs slower than straight-line code, and (hold the thought) why Part III's JIT cares so much about chopping programs at branches.
i3 (amber) is only resolved at EX in cycle 5 — by then the pipeline has already fetched two wrong-path instructions (red), which become bubbles. Fetch restarts at the branch target t1 (cyan).The lab below is that diagram brought to life. A little program flows through five stages; every fifth instruction is a taken branch. With prediction off, each branch flushes the two instructions behind it and the CPI sags. Switch prediction on and the fetch stage guesses the target correctly — watch the bubbles vanish and the CPI settle back toward 1.0.
- A pipeline overlaps the stages of many instructions: same latency each, far higher throughput.
- CPI (cycles per instruction) measures pipeline health; 1.0 is the simple-pipeline ideal.
- Data hazards insert bubbles; branches are worse — the next fetch address isn't known yet.
- Branch prediction keeps fetch busy on a guess; a wrong guess flushes the pipe and wastes the work.
Superscalar — more than one at a time
The pipeline got us to one instruction per cycle. The next question is greedy and obvious: why not two? A superscalar processor fetches and decodes several instructions per cycle and — when they don't step on each other — issues them simultaneously to different execution units. An execution unit is a specialist circuit: an integer ALU, a floating-point unit, a load/store unit that talks to memory, a branch unit. They were always physically separate; superscalar design simply stops making them take turns.
One cashier, one queue: one customer per minute, however fast the cashier. Open a second till and — provided two customers are ready and independent — two leave per minute. But if customer B is paying with money customer A is about to hand them, B must wait whatever the till count. Superscalar issue is the second till; a data dependency is B waiting on A's change.
The catch is the same hazard logic from Module 04, multiplied.
add r5, r3, r4 and
mulli r6, r5, 3 cannot issue together — the
multiply needs r5 first. Real code is a braid of such
dependencies, so a 2-wide machine rarely sustains 2.0 instructions
per cycle. How hard the hardware fights back defines a spectrum:
- In-order machines issue instructions strictly in program order; if the next one must wait, everything behind it waits too. Simple, small, power-efficient.
- Out-of-order machines keep a window of dozens (today, hundreds) of decoded instructions and issue whichever are ready, then use a re-order buffer to make the results appear in program order. Enormously effective, enormously expensive in silicon and watts.
The PowerPC 750 line — Gekko's ancestry — is a modest superscalar: it dispatches up to two instructions per cycle to six execution units (two integer units, a floating-point unit, a load/store unit, a system-register unit, and a branch unit), with only small out-of-order freedoms — a completion queue keeps retirement in program order. Its bet, a very deliberate one, was that a short pipeline plus good caches beats a deep, wide one for the branchy integer code real programs are made of: mispredictions (Module 04) cost only a few cycles instead of dozens. For 2001-era game code, it was a fine bet — and it kept the chip small, cool and cheap, which (Module 07) is exactly what Nintendo was shopping for.
One consequence matters enough to flag now: because issue width is precious, how you arrange instructions matters. Compilers interleave independent work so pairs can issue together. And if one instruction could do the work of two — say, operate on two floats at once — you'd effectively double your width for that code without adding a single issue slot. Hold that thought for exactly three modules.
- Superscalar = fetch, decode and issue 2+ instructions per cycle to parallel execution units.
- Data dependencies, not unit count, are the real ceiling on parallelism.
- In-order machines stall politely; out-of-order machines hunt for ready work at great silicon cost.
- The 750 line is a modest 2-wide, mostly in-order design with six units — small, cool, short-pipelined.
Caches — hiding the memory wall
Here's the dirty secret of the last five modules: we've been pretending memory keeps up. It doesn't — not remotely. While CPUs got faster at a giddy rate through the '90s, memory latency barely moved; by the GameCube's day a processor could execute dozens of instructions in the time one read from main memory took to come back. This widening gap has a name — the memory wall — and without a fix, our beautiful pipeline would spend most of its life stalled at the load/store unit, waiting.
The fix rests on a lucky truth about programs, called locality, and it comes in two flavours. Temporal locality: if you touched a value recently, you'll probably touch it again soon (think of a loop counter). Spatial locality: if you touched an address, you'll probably touch its neighbours soon (think of walking an array). So the hardware keeps a small, fast copy of recently-used memory close to the CPU: a cache. Every load first checks the cache. Found there — a hit — the value arrives in a cycle or two. Not found — a miss — the hardware fetches it from main memory, keeps a copy, and the pipeline eats a stall of dozens of cycles.
A librarian doesn't run to the stacks for every request. Books asked for recently stay in a small pile on the desk (temporal locality), and when a trip to the stacks does happen, the librarian brings back the whole shelf-section, because the next request is usually the book beside the last one (spatial locality). The desk is the cache. The pile's fixed size means keeping a new book evicts an old one — deciding which is the cache's whole art.
Lines, sets and ways
Three mechanisms turn that idea into hardware. First, caches move memory in fixed-size chunks called cache lines — 32 bytes on the 750 family — never single bytes. Miss on one byte and its 31 neighbours ride along free, which is spatial locality made mechanical. Second, each line can live in only a small number of places: the middle bits of its address pick a set (a slot group), and the top bits are stored alongside as a tag so the hardware can tell which line currently occupies the slot. A lookup is: index by the middle bits, compare the tag, hit or miss. Third, to soften the bad luck of two hot lines fighting over one slot — a conflict miss — real caches make each set hold several lines side by side; the number of side-by-side slots is the cache's ways. A cache where every set holds one line is direct-mapped; the 750's L1 caches are 8-way, meaning eight lines share each set and an LRU-style policy picks the victim on eviction.
Two last vocabulary items and you have the full kit. Caches themselves come in layers: a small, split L1 (one for instructions, one for data — so fetch and load never queue for the same door) and a larger, slower L2 behind it. And a miss isn't one thing: compulsory (first touch — nobody's seen this line yet), capacity (the working set is simply bigger than the cache), and conflict (two lines fighting over a set). The lab below makes all three visible: pick an access pattern, watch hits flash green and misses red, and — the key experiment — drag the line size up while running the sequential pattern and watch the hit rate soar as each miss drags more neighbours in. Then try strided and watch a pattern that defeats the cache entirely.
And with that, the Part I toolkit is complete: the loop, registers, number formats, the pipeline, superscalar issue, and caches. Everything in Part II is these six ideas — plus a handful of very deliberate Nintendo-flavoured modifications. Time to meet the chip.
- Main memory is dozens of CPU cycles away — the memory wall is the bottleneck that shapes everything.
- Caches exploit temporal and spatial locality to answer most accesses in a cycle or two.
- Data moves in 32-byte lines; sets and ways decide where a line may live and what gets evicted.
- A hit is nearly free; a miss stalls the pipeline for dozens of cycles — hit rate is everything.
Gekko
Now we put the Part I toolkit into real silicon. Nintendo didn't design a CPU from scratch — it took a proven IBM desktop core and asked for a handful of surgical modifications aimed at one job: feeding a 3D game, sixty times a second. These six modules cover the chip itself and each modification in turn — paired singles, quantised loads, the write-gather pipe, the locked cache — then the unusual memory system the whole console hangs from. Every piece maps back to a fundamental you already know.
Meet Gekko
Gekko is the GameCube's CPU: a custom IBM design derived from the PowerPC 750CXe — the same family Apple was shipping in Macs as the G3. It runs at 486 MHz (three times the 162 MHz system bus; Nintendo's own literature quotes 485), was fabricated on IBM's then-cutting-edge 0.18 µm process with copper interconnects — IBM's signature manufacturing advantage of that era, copper wiring between transistors instead of aluminium for lower resistance — and it embodies every Part I idea: the 4-ish-stage short pipeline (Module 04), 2-wide in-order superscalar issue to six units (Module 05), and a proper cache hierarchy (Module 06): 32 KB + 32 KB of split 8-way L1, with 256 KB of L2 on the die itself — a genuine luxury in 2001, when many CPUs still bolted L2 onto the motherboard.
Why start from an off-the-shelf desktop core at all? Because it's the sane engineering move. The 750 was proven, well-understood by compilers, cheap to manufacture, and — crucially for a small, fanless-quiet console box — small and cool: a short-pipeline, in-order design sips power compared to the deep, hungry designs of its contemporaries. What a desktop core lacked was game-specific muscle: 3D games spend their CPU time on streams of small floating-point vectors, and stock PowerPC processes those one float at a time. So Nintendo and IBM added a targeted set of roughly fifty new instructions — the paired-single family you'll meet in Modules 08 and 09 — plus the write-gather pipe (Module 10) and the locked cache (Module 11). Gekko is best understood as a sensible commuter car with a racing kit bolted on in exactly four places.
Think of the console as a small town. Gekko is the workshop where all general thinking happens — but it is not the centre of town. The centre is Flipper, the companion chip: every road — to main memory, to the graphics hardware, to the audio DSP, to the disc drive and controllers — runs through it, like a central railway station. In PC terms Flipper is the northbridge (and the GPU, and more, all in one). When Gekko wants anything beyond its own caches, it asks Flipper.
One number in that grid deserves a beat of silence: 486 MHz. The PlayStation 2's CPU clocked lower, the original Xbox's Pentium III higher — but raw clocks mislead. What made Gekko effective was the system around it: unusually low-latency main memory (Module 12) meant its cache misses hurt less, and the four custom modifications meant the hot loops of a 3D game — transform the vertices, build the command stream, keep the data flowing — each got a dedicated fast path. The next four modules take those four modifications one at a time.
- Gekko is an IBM PowerPC 750CXe derivative at 486 MHz — 0.18 µm, copper interconnects, small and cool.
- Caches: 32 KB + 32 KB split 8-way L1, 256 KB on-die L2.
- It reaches everything through a 162 MHz front-side bus into Flipper — the northbridge, GPU, DSP and I/O hub in one.
- Nintendo chose a proven off-the-shelf core and added ~50 game-oriented instructions rather than design from scratch.
Paired singles — two floats per instruction
Time for the racing kit. Recall two facts you already own: PowerPC's
floating-point registers f0–f31 are
64 bits wide, sized for doubles (Module 02) — and
games overwhelmingly compute in 32-bit singles
(Module 03). Which means that when a game uses those registers,
half of every register is dead weight. Gekko's designers
looked at that wasted half and asked the obvious question: what if
each 64-bit register could instead hold two singles, and
one instruction could operate on both at once?
That's paired singles. In paired-single mode a
register like f1 is treated as two independent lanes,
called ps0 (the top half) and ps1
(the bottom half). A new family of instructions — ps_add,
ps_sub, ps_mul, ps_madd
(multiply-add), ps_sum0, ps_merge and
friends — performs the same arithmetic on both lanes
simultaneously: ps_add f3, f1, f2 computes
two sums, lane by lane, in the time the plain FPU computes
one. One instruction, two results. This idea has a general name —
SIMD
— and Gekko's 2-wide version is often called
SIMD-lite: narrower than the 4-wide SSE its PC
contemporaries were growing, but cheap in silicon and aimed with a
sniper's precision, as we're about to see.
A chef slicing pasta one cut at a time is the plain FPU. Gekko hands the chef a two-bladed cutter: one stroke, two parallel cuts. It only helps when you want two identical cuts — but pasta, like game maths, is nothing but identical cuts in a row. And note what didn't change: the chef, the arm, the speed of the stroke. Same clock, same pipeline — paired singles simply do twice the work per stroke.
Why is this a sniper shot and not a gimmick? Because of what 3D
game code is. A frame of gameplay is thousands of small
vector operations: every position, velocity and surface direction
is a vec3 — an (x, y, z) triple of
singles — and the engine's inner loops transform, add, scale and
dot-product armies of them, identically, every frame. Skinning a
character, animating particles, culling objects against the camera
— all vec3 streams. Pair x and y in one register (z rides in
another lane, or pairs with the next vertex's x) and a transform
loop's multiply-adds — ps_madd, one multiply plus one
add per lane, the fundamental atom of matrix maths — halve in
count. In hot loops that approaches a clean 2× — an entire second
issue slot's worth of width (Module 05), bought for a fraction of
the silicon.
ps_add adds lane-to-lane in parallel; cross-lane instructions like ps_merge and ps_sum0 shuffle and combine lanes when a dot product needs to fold them together.| Instruction family | Effect (per lane) | Typical use |
|---|---|---|
| ps_add · ps_sub · ps_mul | arithmetic on ps0 and ps1 in parallel | vector add/scale — particles, velocities |
| ps_madd family | multiply-add, both lanes at once | matrix × vertex — the transform loop's atom |
| ps_sum0 · ps_merge | fold or shuffle lanes across registers | finishing dot products; repacking x/y/z streams |
| psq_l · psq_st | quantised load/store into both lanes | Module 09 — this one gets a whole module |
Instruction names as used in Dolphin and the homebrew toolchains; we won't reproduce encodings here — the shapes are what matter.
The lab makes the width visible. A cloud of points spins on screen, but the simulated CPU only has a fixed budget of float operations per frame — never enough for every point. In scalar mode each operation updates one value and the budget runs dry halfway through the cloud; stale points lag behind and the motion smears. Switch to paired and every operation moves two values: same budget, twice the points per frame, and the cloud snaps into smooth, coherent rotation. That difference is exactly what Gekko bought.
- Games compute in 32-bit singles, so PowerPC's 64-bit FPRs were half-empty — Gekko fills them with two lanes, ps0 and ps1.
- ps_* instructions (ps_add, ps_mul, ps_madd…) operate on both lanes at once: 2-wide SIMD, “SIMD-lite”.
- 3D games are streams of vec3 maths, so the doubling lands exactly on the hottest loops.
- Same clock, same pipeline — the win is pure width, like a second issue slot for float code.
Quantised loads & stores
Paired singles doubled the arithmetic. But Module 06 taught you to ask the next question reflexively: can memory keep up? A vertex position is three singles — 12 bytes. A detailed character mesh has tens of thousands of vertices, each with positions, normals, texture coordinates… stored naively as floats, that's megabytes of vertex data marching through a 32 KB data cache and a 162 MHz bus, every frame. The arithmetic is no longer the bottleneck; the bytes are.
The fix comes straight from Module 03. A float's luxury is its enormous range — but a vertex coordinate doesn't need range. A character model fits in a box a few units wide; a texture coordinate lives between 0 and 1. Within a known, small range you can store a value as a small integer with an agreed scale — a fixed-point number. Pick a scale of 26 = 64: then the byte 36 means 36/64 = 0.5625. An 8-bit integer plus an implicit scale replaces a 4-byte float — a 4× saving; 16-bit halves it instead, with 256× finer steps. The mesh's precision drops, but within a small range, 8 or 16 bits of steps is routinely below what your eye could ever notice.
Any CPU can decompress such data — load byte, convert to float,
multiply by 1/64 — but that's three-plus instructions per value,
burning the very cycles paired singles just saved. Gekko instead
does it in the load itself. The instruction
psq_l (paired-single quantised load) reads
two small integers from memory, converts both to
floats, scales both by a power of two, and lands them in ps0 and
ps1 of a register — one instruction, two decompressed values.
Its mirror psq_st quantises and packs on the way out.
Where do the format and scale live? Not in the instruction — in
eight dedicated Graphics Quantisation Registers
(GQRs),
each holding a type (signed/unsigned 8- or 16-bit, or plain float)
and a scale exponent, for loads and stores separately. A
psq_l names which GQR to obey. Eight of them means a
game can leave them parked on its favourite formats — positions in
one, normals in another, texture coordinates in a third — and
stream everything through the right converter with zero setup in
the hot loop.
Shipping assembled wardrobes wastes the lorry — you're paying to transport air. Flat-pack them and one lorry carries four times as many; you assemble at the door. Quantised vertex data is flat-packed: compact in memory and across the bus (the lorry), expanded to full floats only on arrival at the register file. The GQR is the assembly sheet taped to the box: what this is, and how to unfold it.
psq_l pulls two packed integers from memory, and the dequantise hardware — steered by a GQR's type and scale — delivers ready-to-use singles into ps0/ps1. Here: 0x24 = 36 → 36/64 = 0.5625, 0x1B = 27 → 27/64 ≈ 0.4219. The reverse trip is psq_st.The cost of all this is the one Module 03 taught you: the stored steps are coarse, so every value snaps to the nearest representable tick. The lab lets you feel exactly how coarse. Pick a coordinate value and watch its stored bits, reconstructed value and error change as you switch 8↔16 bits and slide the scale. The curve below it is a whole strip of vertices quantised with your settings — at 8 bits and a low scale you can see the wobble that quantisation injects into geometry; at 16 bits it vanishes below the pixel grid. Notice the trade the GQR scale makes: more scale = finer steps but a smaller representable range, the fixed-point bargain in one slider.
- Vertex data lives compressed — 8/16-bit fixed point — because bus bytes and cache lines are scarcer than arithmetic.
- psq_l / psq_st convert and scale during the load/store itself: two values per instruction, free decompression.
- Eight GQRs hold the recipes (type + power-of-two scale), so hot loops never stop to reconfigure.
- The price is quantisation error — the Module 03 rounding story, now applied to geometry.
Feeding Flipper — the write-gather pipe
The vertices are transformed (Module 08) and unpacked (Module 09). Now Gekko has to tell Flipper what to draw, and it does so the way all GPUs are fed: by writing a long stream of small commands — “set this state, here's a vertex, here's another” — into a queue in memory called the GX FIFO (FIFO = first in, first out; GX is the GameCube's graphics API). Flipper's command processor chews through the queue at its own pace while the CPU races ahead. Simple. Except: how should the CPU write it?
Both obvious answers are wrong. Write through the data cache and you poison it — the command stream is thousands of bytes you will never read back, evicting the game's actual working set (Module 06), and the bytes only reach Flipper when lines happen to flush. Mark the region uncached and each little 4-byte store becomes its own lonely trip across the 162 MHz bus — correct, but a hundred dribbles where one pour would do.
Gekko's third way is the write-gather pipe. The game points a special register — the write-gather pipe address register, WPAR — at one magic, uncached address. Every store the program makes to that same single address doesn't go to memory at all: the pipe swallows it into a small on-chip gather buffer, and each time 32 bytes accumulate — one full cache line's worth, no coincidence — the whole block leaves as a single burst transaction to the FIFO. The programming model is almost comic: the entire graphics stream of a GameCube frame is written to one address, over and over, and the hardware turns that dribble into a firehose running at close to the bus's full bandwidth.
Driving to the depot with every single letter is madness — the journey costs the same whether the van carries one letter or a crate. So you drop letters in a crate by the door and the van leaves only when the crate is full. The write-gather pipe is the crate; the 32-byte burst is the full van; the bus makes one trip instead of eight.
The rest of the cache-control toolbox
The write-gather pipe has cousins — ordinary PowerPC cache-control instructions that GameCube games leaned on unusually hard, because a console lets you schedule memory like a freight line:
dcbz— data cache block zero: claim a 32-byte line in the cache and fill it with zeros, without reading memory first. About to overwrite a whole line anyway? This skips the pointless fetch-before-write — a favourite for clearing and building buffers at cache speed.dcbt— touch: a polite hint to start fetching a line now because you'll want it soon — software prefetching for loops that know their future.dcbf/dcbst— flush or write back a line, used when handing a buffer to hardware (like that FIFO) that reads real memory, not your cache.icbi— invalidate an instruction-cache line, mandatory after writing new code into memory. File this one away carefully: it is a load-bearing plot point in Module 14.
- The CPU feeds Flipper by writing a command stream into the GX FIFO — a queue in main memory.
- Cached writes poison the cache; plain uncached writes waste the bus one dribble at a time.
- The write-gather pipe collects stores to one magic address (via WPAR) into 32-byte bursts — near-full bus efficiency with a trivial programming model.
- dcbz, dcbt, dcbf and icbi give games manual control over cache traffic — and icbi returns in Part III.
The locked cache & DMA
Module 06 sold you the cache as a marvel, so here's its dark side: a cache is fast on average and unpredictable in the particular. Any individual load might hit (a couple of cycles) or miss (dozens), and which one you get depends on everything that touched memory before — including hardware you don't control. For most software, who cares; the average is what matters. But a game is a real-time system: it must finish every frame in 16.7 ms, and a physics routine that usually takes 1 ms but occasionally takes 3 is a stutter your thumbs can feel. For the innermost loops, engine programmers would happily trade a brilliant average for a boring, dependable worst case.
Gekko sells exactly that trade. Flip a bit in a hardware
configuration register and the 32 KB data cache splits in
half: 16 KB keeps being a normal cache, and the other
16 KB becomes a locked cache — a
scratchpad
mapped at a fixed address range. “Locked” means the
cache machinery keeps its hands off: nothing is ever evicted,
nothing ever misses, because it isn't caching anything —
it's simply 16 KB of guaranteed-fast memory that belongs
entirely to the program. Every access, every time, costs the same
couple of cycles. (A dedicated dcbz_l instruction
zero-claims lines inside it, cousin of Module 10's
dcbz.)
Of course, 16 KB is tiny — the point is to stream work through it, and that's the second half of the design: a dedicated DMA engine that copies blocks between the locked cache and main memory by itself, in the background, while the CPU computes. The rhythm it enables is double-buffering: while the CPU crunches batch N in one half of the scratchpad, the DMA engine is simultaneously writing out batch N−1's results and pulling in batch N+1. Done right, the CPU never touches main memory at all in the hot loop — no misses, no stalls, no variance. Animation skinning, particle systems, sound mixing on the CPU: classic locked-cache customers.
A chef mid-service never walks to the cellar. An assistant stages the next dish's ingredients at the counter while the current dish is cooking, and clears the finished plates away. The counter is the locked cache — small, close, always two-seconds away; the assistant is the DMA engine; the cellar is main memory. The chef's pace becomes perfectly steady — not because the cellar got closer, but because someone else now does the walking, during the cooking.
The lab races the two philosophies over one frame's worth of work, eight batches long. The top lane computes straight from the normal cache: every batch gets interrupted by whatever misses fate deals out (slide the miss-chance to change fate). The bottom lane computes from the scratchpad while DMA prefetches the next batch underneath. Watch the red stall slices — and notice something subtler than the finish time: replay it a few times. The top lane's finish moves; the bottom one lands on the same spot, every single run. That repeatability is the real product.
- Caches are fast on average but unpredictable per-access; real-time frames care about the worst case.
- Gekko can lock half the 32 KB D-cache as a 16 KB scratchpad: fixed addresses, constant latency, zero misses.
- A dedicated DMA engine streams batches between scratchpad and main RAM while the CPU computes.
- The prize isn't just speed — it's determinism: the same loop takes the same time, every frame.
The memory system
We've spent five modules teaching Gekko to avoid memory. Time to be fair to the memory — because the GameCube's is genuinely unusual. Its 24 MB of main RAM, code-named “Splash”, is 1T-SRAM from a company called MoSys — a clever hybrid that stores bits in dense DRAM-style cells but hides all the usual DRAM ceremony (row opens, refresh pauses) behind SRAM-like interface circuitry. The result is main memory with about 10 ns latency — roughly five Gekko clocks — where contemporary PC DRAM of the era, with its row and bank management, could keep a CPU waiting several times longer, and less predictably. Sound familiar? It's the Module 11 value again: Nintendo consistently bought low, boring latency over headline bandwidth. A cache miss on GameCube simply hurts less than it did on its rivals.
Beside it sits an oddity: 16 MB of ARAM (auxiliary RAM) — plain, cheaper, slower DRAM hanging off Flipper on its own narrow interface. Gekko cannot address it directly: no load or store ever touches ARAM. Instead, blocks move between ARAM and main memory by DMA through Flipper, and the audio DSP streams instrument samples from it (that story is told properly in the companion audio course). Games treat it as a staging attic: audio banks live there, and clever engines park level chunks, animation data and pre-loaded assets in it — anything worth keeping closer than the optical disc but not worth precious Splash space. The disc, for scale, streams in at a few MB/s with seek times measured in tens of milliseconds: a different world, which is why ARAM earns its keep.
The kitchen's pantry (main RAM) is small but two steps away — anything in it is essentially at hand. The cellar (ARAM) holds more, but fetching from it is a planned trip — you send someone (DMA) and keep cooking meanwhile. And the market (the disc) has everything, once a day, if you queue. A good kitchen — and a good game engine — is mostly logistics between the three.
Step back and admire the whole Part II machine at once. A frame of
a GameCube game is: psq_l streams compressed vertices
from Splash through the cache (09), paired singles transform them
two floats at a time (08), the write-gather pipe fires the results
into the GX FIFO for Flipper (10), the locked cache keeps physics
and animation running on rails (11), DMA shuttles the next second
of audio out of ARAM (12) — and underneath it all, the plain old
fetch–decode–execute loop from Module 01 is just… running,
486 million times a second. Nothing mystical was added; every
trick is a Part I idea, sharpened. Which sets up Part III's
question perfectly: if it's all just the PowerPC contract plus
four tricks — can a PC pretend to be it?
- Main memory is 24 MB of MoSys 1T-SRAM (“Splash”) at ~10 ns — unusually low, predictable latency for its day.
- 16 MB of slower ARAM hangs off Flipper, reachable only by DMA — audio samples and staged assets live there.
- Flipper is the hub: memory controller, GPU, DSP and I/O; all of Gekko's traffic crosses one 162 MHz bus.
- Every Gekko modification is memory-ladder logistics — the console's consistent bet was predictability over peak numbers.
Emulating it in Dolphin
Finally: how does software on your PC stand in for that chip? Your processor speaks x86-64 or ARM64 — it cannot run a single PowerPC instruction. Dolphin's answer is to translate, and the way it translates — an interpreter for correctness, a just-in-time compiler for speed — is one of emulation's great stories. These three modules cover the machinery, the traps where “fast” and “accurate” part company, and the actual settings that map to everything you've learned.
Interpreters vs JITs
Strip the problem to its core: Dolphin holds the game's memory in a
big array, and in that array sit PowerPC instructions — numbers,
as Module 01 taught you. Your CPU can't execute them. The direct
solution is an interpreter: a software re-enactment
of the fetch–decode–execute loop. Keep a variable for the PC and
one for each of Gekko's registers; loop forever: read the word at
PC, look at its opcode bits, jump to the matching handler
(“this is an add: sum these two array slots”),
bump PC, repeat. It's Module 01's toy lab, scaled up — Dolphin
genuinely contains one, and it is the reference: nearly
perfectly faithful, easy to reason about… and brutally slow.
How slow? Count what one emulated add costs: fetch the
word from the memory array, mask out the opcode bits, branch
through a dispatch table (a branch your host's predictor hates —
Module 04 again, one level up!), load two virtual registers, add,
store the result, update the virtual PC, check for interrupts, loop.
Ten to fifty host instructions for one guest instruction — and
here's the crime — the same ten to fifty again the next
million times that same add runs. A game loop is the same few
hundred instructions repeated all frame. An interpreter re-derives
the same conclusions about the same bytes, forever.
The fix is to stop re-deriving: translate once, remember, reuse. That's a JIT — a just-in-time compiler. The natural unit of translation is the basic block: a straight run of guest code from one branch target to the next branch (Module 04 taught you why branches are the natural seams — they're the only places control can leave). First time execution reaches a block, Dolphin translates its PowerPC instructions into equivalent x86-64 (or ARM64) machine code, writes that into a buffer, and stashes a pointer in the block cache, keyed by guest address. Every later visit is a lookup and a jump: the loop that cost 50× interpreter overhead now runs as a handful of native instructions — often just 2–5× slower than truly native code, hundreds of times faster than interpreting.
An interpreter is a live translator at a speech that's read out every night: each night they translate the same sentences again, from scratch, at speaking pace. A JIT writes the translation down the first night. Night one is actually slower — translating and writing carefully takes longer than just speaking — but every night after, they simply read. By night three they've won; by night ten thousand (a game loop's real repeat count) it's no contest.
Two refinements complete the picture. First, there's a middle
rung: a cached interpreter, which decodes each
block once into a friendly internal list (skipping the re-decode
cost) but still executes it by dispatching — faster than plain
interpretation, far slower than a true JIT, and useful when the
JIT itself is the thing you're debugging. Second, the block cache
is only valid while the guest code doesn't change. Games
load code overlays from disc, decompress engines into RAM, even
patch themselves — and the moment those bytes change, a cached
translation of the old bytes is a lie. The JIT must detect writes
to translated code and invalidate the affected
blocks — remember icbi from Module 10? That
instruction is the guest politely announcing “I changed the
code here” — and the next visit recompiles. Getting this
wrong doesn't slow the emulator; it makes it wrong, which
is Module 14's whole subject.
The lab races the two strategies over a hot loop. Watch the JIT lane pay its violet compile toll up front — at low repeat counts it actually loses — then drag the run-count slider up and watch amortisation do its work. The block-cache chip lights up the moment the translation exists.
- An interpreter re-runs fetch–decode–execute in software: faithful, simple, 10–50 host instructions per guest one.
- A JIT translates each basic block to native code once, caches it, and reuses it — amortisation is the whole trick.
- Basic blocks end at branches; the block cache maps guest addresses to translations.
- When guest code changes (overlays, self-patching, icbi), stale blocks must be invalidated or the emulator lies.
The accuracy traps
If Module 13 made JITs sound like a solved problem, this module is
the cold shower. Translating an add is easy. The hard
part is everything a real Gekko does implicitly — the
exact float bits, the passage of time, the caches, the address
translation — which software must now reproduce explicitly,
and every explicit check costs cycles in the hottest loop on the
machine. Accuracy isn't one dial; it's a series of specific traps,
and each one is a place where Dolphin's developers had to choose
speed, truth, or some painstaking third path that buys both.
Trap 1 · Floating point is a bit-exact contract
Module 03 warned that every float is a rounded approximation.
Here's the sting: which approximation is part of the ISA
contract, down to the last bit — and Gekko's paired singles are a
custom extension with their own personality. Their operations
round to single precision at each step, while a straight x86
translation might compute in double and round once — same maths,
different final bit. ps_madd's
fused-or-not rounding behaviour, the exact bit pattern a NaN
propagates, denormal handling, the guest's rounding-mode flags:
each can differ subtly from the host's native habits. Does one
wrong low bit matter? In a screenshot, never. In a physics engine
run 60 times a second for an hour — where this frame's output is
next frame's input — divergence compounds: speedrun replays
desync, and a famously fragile few games misbehave. Dolphin's JIT
therefore massages float code with extra host instructions to
force Gekko-truthful rounding — a real, measured cost paid for
invisible correctness.
Trap 2 · Time is part of the machine
A real Gekko takes real time: 486 million cycles per second, no more, no less, and games are written against that meter. Consider the idle loop: when a frame's work is done early, the game literally spins — branch-to-self — until the video hardware signals the next frame. Run that “as fast as possible” and the emulator melts a host core doing nothing; so Dolphin detects idle spins and skips ahead in virtual time. But virtual time must stay coherent: game logic paced against the CPU clock, video interrupts, DMA completion, the audio DSP — all advance on one shared timeline, and code that finishes “too fast” relative to it can break assumptions the original developers never knew they were making. This is why an emulator can't simply hand the game more speed: correctness is keeping the clocks in step, and speed is only welcome where no one can observe it.
Trap 3 · Code that rewrites itself
Module 13 flagged it; here's why it's treacherous. On real
hardware, writing to code memory just works — the game flushes
with dcbf/icbi (Module 10) and the
icache refills. The hardware doesn't need to notice
writes; the JIT does, or it keeps executing a ghost. Dolphin
leans on the guest's own icbi etiquette and on
tracking writes to pages holding translated code — but games load
overlays, unpack compressed code, and a few scribble over
themselves with abandon. Miss one write and the symptom appears
minutes later, somewhere else entirely: the classic
hardest-bug-in-emulation.
Trap 4 · The MMU, or: which memory is that, exactly?
One piece of real hardware we've politely ignored: the MMU, which sits between every address a program uses and the physical RAM it lands in. Most GameCube games use PowerPC's simple block-mapping mode — a handful of big fixed windows onto the 24 MB — which an emulator can translate with one fast mask. Dolphin's fast path builds on that, backed by clever use of host page-fault machinery to catch the exceptions. But a minority of titles genuinely exercise page-granular mappings and expect precise page faults — memory exceptions delivered at exactly the right instruction — and for those, Dolphin offers a per-game full-MMU mode: more checks on every memory access, slower, and for a stubborn shortlist of games, the difference between booting and not.
A flight simulator that only looks right is a video game; one that a licensing examiner accepts must match the aircraft's numbers — fuel burn, stall speed, instrument lag — because pilots build reflexes against the real machine's behaviour, not its appearance. Games are the same pilots: a million accidental dependencies on the exact float bits, the exact cycle counts, the exact fault timing. Fast gets the plane in the air; accurate is what keeps two decades of software from noticing it ever changed aircraft.
icbi etiquette. A JIT must reproduce that vigilance in software, on every write, forever.Why not just always be maximally accurate? Because every trap above taxes the innermost loop. Bit-true float rounding adds host instructions to every ps_* op; full MMU adds checks to every load and store; paranoid write-tracking taxes every store. Each is a few percent — together they can halve the frame rate. Dolphin's art is defaults that are truthful where games can tell and fast where they can't, with per-game overrides for the stubborn exceptions. That's the exact trade you'll set with your own hands in the final module.
- Float accuracy is bit-exactness: paired-single rounding, NaN bits and mode flags must match, or divergence compounds frame over frame.
- Time is architectural: idle loops can be skipped, but all virtual clocks must advance in step.
- Self-modifying code makes cached translations lie; icbi etiquette plus write tracking is the JIT's lifeline.
- Most games live happily on fast block-mapped memory; a few demand full MMU emulation with precise faults.
Try it in Dolphin
You now have the full picture, so let's make it hands-on. Dolphin's CPU-related options live in its general and advanced configuration (plus per-game property overrides); names shift slightly between versions, so we'll describe each control by what it does — which, pleasantly, you can now explain from first principles:
- CPU emulation engine → JIT recompiler — the default, and Module 13's whole argument: translate each basic block once, run native ever after. There's one per host architecture (x86-64, ARM64). Leave it here unless you have a reason.
- → Cached interpreter — the middle rung: decode once, dispatch forever. A handy A/B point when you suspect the JIT itself of a bug — same timing model, none of the code generation.
- → Interpreter — the reference re-enactment. Wonderful for developers bisecting a miscompiled instruction; unplayably slow for actual games. Running it once, briefly, is the best visceral proof of Module 13 you'll ever get.
- Clock override — a slider that changes the virtual CPU clock away from 486 MHz. Underclock and honest games drop frames; overclock and games whose logic is paced by frame interrupts stay identical while ones that busy-wait or self-pace can speed up, glitch — or fix a slowdown the real console always had. It's Module 14's “time is architectural” lesson with a physical handle.
- MMU emulation (per-game) — full page-granular address translation with precise faults, for the short list of titles that demand it. Costs speed on every access; games that need it won't boot honestly without it.
- Dual core / synchronise-GPU-style options — run the emulated CPU and GPU on separate host threads (fast, slightly loose timing) or tightly synchronised (slower, deterministic). Exactly the Module 14 trade: speed where games can't observe it, truth where they can.
Before this course, Dolphin's settings panel was a wall of unlabelled faders — you moved one and hoped. Now you've been behind the desk and traced every wire: the engine dropdown is Module 13's interpreter-to-JIT ladder, the clock slider is Module 14's virtual time, the MMU box is trap four. Settings stop being superstition the moment you know what each fader is soldered to — the diagram below is simply that wiring, read in the order a real troubleshooting session walks it.
Three experiments worth actually running
- Feel the interpreter. Take any game, switch the engine to interpreter, and watch the frame rate collapse — then flip back to JIT. That difference is Module 13: you just toggled amortisation on and off.
- Bend time. Set the clock override to 150%, then 60%, in a game with a busy pause menu. Some things speed up, some don't — sorting which is a live lesson in what's paced by the CPU clock versus the frame interrupt (Module 14).
- Read a JIT block for real. Dolphin ships a debugger with a JIT view showing guest PowerPC beside emitted host code. Find a loop, and you'll recognise everything: the basic block, the boundary branch, even ps_madd sequences from Module 08 — the whole course, on one screen.
And that's the course. You began with a cook and a stack of numbered cards; you end able to read a Gekko datasheet, explain why half a cache became a scratchpad, and predict which Dolphin checkbox fixes which symptom. The fetch–decode–execute loop is still down there, under everything, turning — on a 2001 games console, on the machine you're reading this with, and in the emulator faithfully pretending to be one on the other.
- You can trace a frame end-to-end: quantised loads → paired-single maths → write-gather pipe → Flipper — with the locked cache keeping time.
- You know what a JIT actually does, why it's fast, and the four traps where accuracy costs real speed.
- You can map every CPU setting in Dolphin to a mechanism you understand — and predict what toggling it will do.
- Want Gekko to run your code next? The homebrew course takes you from an empty folder to a program of your own on this CPU.