FPGA console · engineering project

Zhaozhou

A 240p-class, fixed-function 3D console whose detail is allocated by projected pixel importance rather than permanently attached to objects. Deformable terrain that records battle damage. Hard 60 Hz. Two-player split-screen. Its signature game is an action-RTS in the lineage of Sacrifice.

What you are looking at. Every image on this page is a ZRef reference-oracle render — output from the project's bit-exact C++ software reference renderer. There is no FPGA board in this project. No image here came off hardware, and none is a simulation of hardware. The oracle is the specification the silicon will be held to: when RTL lands, it must reproduce these canvases pixel-for-pixel, checked by CRC.

01 — THE MACHINEWhat Zhaozhou is

Zhaozhou is a console architecture designed for the SuperStation One — a MiSTer-family board built on a Cyclone V FPGA. It is not a miniature PC GPU. The charter puts it plainly: it is “a purpose-built machine for transforming small authored inputs into overwhelming geometric motion.”

The defining idea is an inversion of how detail normally works. On a conventional machine, a model carries its own level of detail: an object is “high poly” or “low poly” as a property of the asset. On Zhaozhou, detail is a budget allocated by projected pixel importance — how much screen a thing actually occupies this frame decides how much geometry it gets. An army of a hundred creatures collapses continuously from full geometry to tiny meaningful moving forms, and nothing about the asset changes.

The second idea is that the ground remembers. Terrain is a live heightfield driven by field programs — small deterministic bytecode programs that deform it — plus a persistent per-patch surface sheet that records scarring. Craters, ridges, trenches and travelling waves are not decals. They are the terrain, and they stay.

What it is for

The three frozen display modes

Mode3D render areaScanout canvasPurpose
Z60384 × 240384 × 240Primary single-player mode
Storm320 × 240320 × 240Maximum overdraw, particles and transparency
Duo2 × 256 × 192512 × 240Two 4:3 views plus 48 scanlines of cheap 2D interface space

Duo costs almost nothing extra to raster: 98,304 3D pixels against Z60’s 92,160 — 6.7% more. The second camera adds geometry work, but simulation, terrain deformation, creature animation and particle simulation stay shared. Split-screen was designed in from the start rather than bolted on.

The laws that do not bend

03 — STATUSWhere the project actually is

Every number below was read out of the repository. Nothing here is rounded up, and the zeroes are as load-bearing as the rest.

87
blocks in the design ledger
57
ctest tests registered
115
TypeScript test cases
0
blocks synthesized or above
0
FPGA boards in this project

The maturity ladder

Every block advances one rung at a time, and each advance must be justified by pinned evidence that a validator checks against git history. The ladder has seven rungs. The project stands on rung four, for six blocks out of eighty-seven.

SPECIFIED75
REFERENCE_COMPLETE3
UNIT_VERIFIED3
RTL_VERIFIED6
SYNTHESIZED0
INTEGRATED0
HARDWARE_PROVEN0

Of the 87 blocks, 72 are FPGA/RTL blocks and 15 are software. The six at RTL_VERIFIED are the memory and I/O spine — the VRAM arbiter, the HPS bridge, the memory guard, the input snapshot and rumble paths, and the audio FIFO — verified in simulation against their C++ references, with formal properties where the property is expressible.

Six further blocks carry blocked_on: hardware (SYS.PLL, SYS.RESET, SYS.CDC, MEM.SDRAM, SW.TOOLS.REPORT, SW.TOOLS.BOARDPROBE). The ledger validator refuses to advance them past SPECIFIED no matter what evidence is offered, until the hardware lane opens. That is a mechanically enforced honesty rule, not a convention.

Tests

SuiteCountWhat it covers
ctest, total registered57the whole C++/RTL lane
  · labelled fast47the gate every change must pass
  · labelled nightly43long randomized soaks
  · labelled lint12RTL and package lint gates
  · labelled formal6bounded model checking (skips cleanly without a solver)
TypeScript test cases11512 files across 4 npm workspaces
SystemVerilog files under fpga/rtl129 modules + 3 packages
Code, excluding generated output~38,600lines of C++, TypeScript and SystemVerilog

Phases

The charter defines fourteen phases, 0 through 13, each with a hard acceptance gate. One phase has a written gate report. Substantial merged work exists beyond it — memory, input, audio, the Form language frontend, the whole software renderer these images come from — but no gate report covers it, so it is not claimed as gated.

0Board truth — probe the real hardware before freezing any numberBLOCKED · no board
1Specification, Form IR and oracle skeleton6 of 7 gates green
2Console shell — Z60/Storm/Duo timings, double-buffered scanoutwork merged, not gated
3Software console and minimal Form languagework merged, not gated
4First exact tile and triangle — the RTL rasterizer beginsnot started
5Opaque textured 3D corenot started
6Static Mantle and visible surface damagenot started
7Live terrain deformationnot started
8Geometry frontend and The Measurenot started
9Creatures and Transform Loomnot started
10Myriad polygon stormnot started
11Primitive Forge and final 2D effectsnot started
12Production language and asset pipelinenot started
13Closure — timing, soak, cartridge and release flownot started

The one outstanding Phase-1 gate is “one tiny Form program parses, type-checks and lowers to deterministic C++”, deliberately deferred to Phase 3 by the build plan. The report that records this says so itself, in writing.

First synthesis

Synthesis has been run exactly once, and not on the console.

MetricResult
ToolchainQuartus Prime 17.0.2 Lite
Device (provisional)Cyclone V 5CSEBA6U23I7
ALMs≈ 51
Registers96
Pins92
DSP blocks0
M10K blocks0
Errors0

Three caveats, all of which matter more than the numbers. The design under synthesis is the ABI probe — a 52-line streaming CRC-32C engine over the generated ABI package — not the console. The part is provisional: the real device ordering code is Phase-0 board truth and is not yet known. And the numbers live in a commit message rather than a parsed report, because the report parser is deliberately untested until it has a real Quartus fitter report to eat. Consequently no block records a measured resource figure, and no block is SYNTHESIZED.

There is no timing closure data and no bandwidth data. Those directories exist and are empty on purpose.

04 — BLOCKEDWhat needs physical hardware

The repository keeps a written list of everything it cannot honestly do without a board, and refuses to guess any of it. This is that list.

And what simply is not built yet

05 — METHODHow it is being built

The interesting part of this project is not the renderer. It is the rule that nothing is allowed to exist as hardware until it already exists as something testable.

Every hardware block must have, before any RTL is written: an executable reference implementation, directed tests, randomized differential tests against that reference, and formal properties where a property can be stated. A block advances one rung at a time and each advance is pinned to evidence a validator re-checks against git history. Six blocks are blocked on hardware and no amount of evidence can move them — the tool enforces it.

The command ABI is generated from a single interface definition into C++, TypeScript and SystemVerilog, and the three are held byte-identical by a conformance test that fails on any drift. The field-program interpreter exists twice, in C++ and TypeScript, and a fuzz-parity test compares them on generated corpora. Frame packets are sealed, validated fail-safe, and replayed; canvases are pinned by CRC-32C. There are no floating-point operations anywhere in the render path — the software renderer is integer-only precisely so that hardware can match it exactly rather than approximately.

The part that is easy to get wrong

Differential testing catches disagreement, not shared error. Adversarial review rounds — reading the code against the frozen specification, hunting for defects the suite cannot see — have found real bugs while the entire test suite was green:

A 216 amplification in the spline operation. Both interpreters computed the final Catmull-Rom term as v · 215 instead of v / 2. Every non-knot input saturated to the maximum representable value. Spline knots evaluate with v = 0, which hid it — and the C++/TypeScript differential test compared two implementations that shared the identical mistake, so it could not possibly have caught it.

A sky-geometry collapse that made the sky invisible. The drum column angle was computed with a shift that is zero for every column, so all 48 columns shared one angle. All 1,536 band triangles degenerated to vertical lines and the rasterizer dropped every one of them at the zero-area check. The 360° skybox silently did not exist, while the layer-census, UV and scroll tests all stayed green — they counted primitives and checked texture coordinates without ever asking whether a pixel had been drawn. The fix came with an assertion that pins the geometry itself: zero band pixels before, 371 after.

An invisible sun, in the same review. A hand-rolled fixed-point-to-byte narrowing produced 256, which truncates to 0 in a byte — so for any sun energy above one third, the additive quad contributed exactly nothing. The commit message notes this is the same defect class as an earlier finding, reintroduced by writing a second implementation of a frozen conversion instead of calling the one that already existed.

Also caught: a fill rule that dropped every shared-edge pixel on both sides of a seam, a sine table read one entry past its end, and world-space markers that grew with distance instead of shrinking. Each fix ships with a test that fails if the bug is reintroduced.

The lesson the project keeps re-learning, and keeps writing down: a green suite is evidence about the tests, not about the machine. Counting primitives is not the same as checking that a pixel was written.