Game of Life
Cellular AutomataConway's Game of Life
running live on a Super Nintendo. A 128×112 grid of cells evolves by the
B3/S23 rule — a cell is born with exactly 3 live neighbours and survives with 2 or 3.
A Gosper glider gun in the top-left fires a glider every 30 generations down into a
random soup that settles into still-lifes and blinkers. The cells are bit-packed (8 per
byte) and their neighbour counts are computed bit-parallel — no multiplies, no divides.
Written in C and compiled with the
llvm-mos-based 65816 toolchain
(+mos-a16, 16-bit accumulator). Runs automatically.
Self-running demo — the gun fires automatically; watch gliders cross the field.
Click the screen, then play. (Tab away and it pauses.)
What it is
John Conway's Game of Life (1970) is the most famous cellular automaton: a grid of cells that live or die each generation by counting their eight neighbours. From those two rules emerge gliders, oscillators, and — in Bill Gosper's glider gun — patterns that grow without bound, firing a stream of gliders forever. The whole step is one pass over the grid:
for each cell:
n = count of live cells among the 8 neighbours
next = (n == 3) or (alive and n == 2)
The trick on an 8-bit CPU is to do all eight cells of a byte at once. Each of the eight
neighbour directions is a packed byte (after a cross-byte shift to align it); the eight bytes
are summed bit-parallel into a 4-bit-per-cell count held across four bitplanes
(c0=1s … c3=8s) with a ripple of half-adders. The rule then becomes a
handful of boolean selects: count==3 is
~c3 & ~c2 & c1 & c0, count==2 is
~c3 & ~c2 & c1 & ~c0.
Why it stresses the compiler
Most demos in this battery lean on 32-bit multiply/divide libcalls. Life is the opposite — it
is deliberately multiply- and divide-free, so it exercises the parts of the backend the
arithmetic demos don't: dense boolean logic (and/eor/
ora across the SWAR full-adder), constant-amount shifts
(asl/lsr for the cross-byte neighbour alignment), indexed array
traffic over three row pointers, and the two-buffer ping-pong swap. It is the
2-D companion to the 1-D Rule 90/110 automaton.
Compiler stress-test
| Item | What it exercises |
|---|---|
| Bit-packed grid | The 128×112 field stores 8 cells per byte — a packed byte is also a 2bpp bitplane byte, so the renderer copies grid bytes straight into tile memory with no conversion |
| SWAR neighbour sums | Each byte’s eight neighbour bit-vectors are added bit-parallel into a 4-bit-per-cell count with a ripple of half-adders (carry = a & b; sum = a ^ b) — pure and/eor/ora, no per-cell branch |
| Cross-byte shifts | Left/right neighbours come from (m >> 1) | (Lbit << 7) and (m << 1) | Rbit, pulling the boundary bit from the adjacent byte — constant-1 asl/lsr + ora |
| Two-buffer swap | A classic double-buffer ping-pong (use_a ^= 1) reads one grid and writes the other, then swaps — no per-generation copy |
A separate 64×48 gate grid seeded with the glider gun runs 32 generations and folds every
output row into a CRC-16 after each one. The gate asserts corpus_result
(0xDDF1) is identical on host, default 8-bit SNES, +mos-a16, and
+mos-xy16. The Verify fidelity button above reproduces that gate live in
this tab via the same cycle-accurate bsnes-jg core used in CI.
Written in C with the llvm-mos 65816 toolchain. Hit Verify fidelity
to reproduce the build gate's WRAM assert (corpus_result = 0xDDF1) live in
this tab. No far pointers — the same logic checked four ways: host == default ==
+mos-a16 == +mos-xy16, with bsnes-jg as the in-browser
confirmation.