FPGA proving ground · silicon is the goal

Zhaozhou

A 240p-class, fixed-function 3D console: an FPGA proving ground now, fabricated silicon as the goal. Its detail goes where the camera is actually looking, its terrain permanently records battle damage, and its signature game is an action-RTS in the lineage of Sacrifice.

What you are looking at. Every image on this page comes from the project's reference renderer: a deliberately slow, integer-only C++ program that defines what the hardware must eventually produce, pixel for pixel. There is no FPGA board in this project, and nothing in it has ever run on a physical device. What does exist is a growing set of circuit designs, and every one of them is held to this renderer in simulation: the assembled console shell reproduces its output byte for byte, checked every frame, and each newer block is compared against it case by case. These images are the target the silicon will be tested against.

01 · THE MACHINEWhat Zhaozhou is

Zhaozhou is a console architecture aimed at real, fabricated silicon: a physical machine of its own. The working proving ground along the way is an FPGA core for the SuperStation One, a MiSTer-family board built around a Cyclone V FPGA. That core is a step on the road, not the destination. It is not a miniature PC graphics card. The project charter calls it “a purpose-built machine for transforming small authored inputs into overwhelming geometric motion.”

The defining idea inverts how detail normally works. On a conventional machine, a model carries its own level of detail: an asset is “high poly” or “low poly” by nature. On Zhaozhou, detail is a budget, allocated by how much screen a thing actually occupies this frame. An army of a hundred creatures collapses smoothly from full geometry down to tiny meaningful moving forms as it recedes, and nothing about the assets changes.

The second idea is that the ground remembers. Terrain is a live surface deformed by small deterministic programs. Craters, ridges, trenches and travelling waves are not painted-on effects; they are the terrain itself, and they stay.

What it is for

The three 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 a strip of cheap 2D interface space

Split-screen was designed in from the start, not bolted on: Duo draws only 6.7% more 3D pixels than the single-player mode (98,304 against 92,160), and the simulation, terrain deformation and particles are shared between the two views.

The rules that do not bend

03 · DIARYWhat the machine looked like, and when

The gallery above always shows a subject's current state. This diary is the half that never changes: an entry is recorded once, on its date, and is never overwritten or deleted. Each group below follows one subject, newest first, and links back to that subject's current render in the gallery. Because every capture is deterministic, an entry is also a regression artifact: a loop that has drifted no longer matches its recorded self.

terrain-breach

1 entry · 2026-08-16 · See this subject's current render in the gallery.

Diary entry, 2026-08-16, subject terrain-breach: The breach captured while the island was still a flat 22 m sheet with flat-shaded colour surfaces. The dig met the thin underside quickly and debris left the world almost at once; the current gallery loop digs 84 m through a 75 m keel of banded, textured rock.

2026-08-16 · flat island 384 × 240 (Z60) · 64-frame loop · shown at 3×

The breach captured while the island was still a flat 22 m sheet with flat-shaded colour surfaces. The dig met the thin underside quickly and debris left the world almost at once; the current gallery loop digs 84 m through a 75 m keel of banded, textured rock.

flare-occlusion

1 entry · 2026-08-16 · See this subject's current render in the gallery.

Diary entry, 2026-08-16, subject flare-occlusion: The occlusion fade captured against the flat 22 m island, before the keel and before surfaces were textured. The current gallery loop runs the same fade against the deep, textured island.

2026-08-16 · flat island 384 × 240 (Z60) · 64-frame loop · shown at 3×

The occlusion fade captured against the flat 22 m island, before the keel and before surfaces were textured. The current gallery loop runs the same fade against the deep, textured island.

04 · STATUSWhere the project actually is

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

88
blocks in the design ledger
214/214
fast test gate, zero failures
205,026
checks in the 10,000-frame soak, zero failures
0
blocks synthesized or above
0
FPGA boards in this project

The maturity ladder

The design is split into 88 blocks: 73 hardware, 15 software. Every block climbs a seven-rung ladder one rung at a time, and each advance must cite evidence that a validator re-checks against git history. Nothing is on the top three rungs, because those require a physical board, and there is none.

SPECIFIED37
REFERENCE_COMPLETE4
UNIT_VERIFIED33
RTL_VERIFIED14
SYNTHESIZED0
INTEGRATED0
HARDWARE_PROVEN0

The fourteen blocks at RTL_VERIFIED are the whole console shell: command scheduling and DMA, the memory arbiter, guard and bridge, the video pipeline, input, audio, and the debug counters and per-frame checksums, each verified in simulation against its C++ reference. The thirty-three at UNIT_VERIFIED are the drawing machine and the world it draws: the five rasterizer blocks, the texture units and cache, the geometry front that clips and bins a triangle, the terrain blocks, the persistent damage store, the creature skinning block, the vertex projector, two particle blocks and the trace ring. That rung means the circuit design and the C++ reference agree on every case a test could put to them, in simulation. Six further blocks are marked blocked on hardware; the ledger tool refuses to advance them until a board exists, no matter what evidence is offered.

The creature path now exists as circuits

The machine could draw a world, but not an animal moving in it. That path is now designed and checked in simulation from end to end: a packed quaternion becomes a rotation matrix, bone matrices chain down a skeleton, a whole skeleton pose is decoded from one animation clip and frame, a cache decides which poses are already decoded and which one to throw away, and skinning makes a creature's vertices follow its bones. Each piece is compared against the same C++ reference the images above come from.

Two decisions inside it are worth stating. The chain multiply is built to compute one element per cycle rather than all of them at once, and the whole decode chain shares a single multiply engine across every bone, because posing is not per-vertex work: a pose is decoded once for a given creature type, animation and frame, and then shared by every creature of that type on screen that frame. That sharing is the entire reason the cache exists. The cache block, in turn, decides which pose is needed and where it goes while deliberately not owning the memory it sits in, because how much of the chip's fast memory to spend on posing is a real decision, and burying it inside a module is how a budget gets spent without anyone choosing. Until that is decided the pose block stays marked unfinished, because two verified halves are not a finished block.

A vertex on screen, and a division that had to be exact

Turning a posed vertex into a pixel position needs a division, and the circuit has to give the same answer as the reference down to the last bit, including how it rounds negative numbers, where the obvious approach is subtly wrong. No reciprocal trick reproduces it, so the block carries a real thirty-one step divider, pipelined so it still retires one vertex per clock. Its declared reference did not exist when the work started; the law turned out to be shipped under another name, already proven by the terrain block that uses it.

Around it, a run of smaller blocks landed the same way: the trace ring that records what the machine did for later inspection, the store that holds the scars burned into the ground, the two terrain blocks that bake a program's output into the surface and read velocity back out of it, a cache that keeps validated spell programs resident, and the two ways a particle becomes something drawable, a triangle when it is large and a point sprite when it is small.

The scar store had working circuitry and a green test suite for months and still could not advance, because the ledger looked at its test and called it an alias rather than evidence. It was right: the test imported the reference model, named it, and never called it, so it checked that the block agreed with itself. The replacement runs both over one stream of operations and makes them agree on every texel of a patch. The law it defends is the one the block exists for, that re-acquiring a patch does not wipe it. A version that cleared on every acquire passes any test that writes and reads in one go, and erases the world between frames.

The spell-program engine

Terrain programs and spells are compiled bytecode, and so far only software has been able to run them. The engine that will run them in hardware is under construction, and four parts of it are built and checked: an arithmetic core covering fifteen operations (add, multiply, compare, clamp, dot product and the rest) over 300,000 random cases; a reciprocal, which needs a 256-entry table and a correction step, over 150,000; sine and cosine, checked exhaustively, because an angle here is a 16-bit count of turns, so all 65,536 of them were compared for both functions, which makes that one a proof rather than evidence; and length and distance, resting on an exact integer square root done as binary longhand rather than an approximation, because a program's lengths have to match the software's at every input.

Both tables are generated from the reference by a script instead of being typed in, and then checked entry by entry, because a wrong constant would not fail loudly. It would put a few angles slightly off. An operation the engine cannot yet perform refuses rather than returning zero, because a quiet zero would let it run a spell it cannot actually evaluate and everything would look fine.

One engine unblocks five blocks, which is why it is the next substantial thing being built. The specification forbids a second implementation of the instruction set anywhere in the project, so this is a bytecode engine that executes the same program images the software interpreter reads, verified against that interpreter, rather than an evaluator written by hand from the prose.

What is left, and who it is waiting on

Every remaining block was trial-advanced one at a time and its objections written down, so the remainder is known rather than estimated. Two supplies have now run out: work that was finished but merely unrecorded, and blocks whose law was already written somewhere under a different name. The thirty-seven that remain split three ways.

Finishing the hardware is therefore not one more sitting's work, and the page would rather say so than let a rising count imply otherwise.

Phase 2 closed: the console runs as one machine

Before Phase 2, every verified block had only ever been tested alone. Phase 2 composed them into one running machine: publish a sealed frame packet and the console fetches it, schedules it, copies the pixels, swaps buffers, scans out, and reports a per-frame checksum and counters, with no test scaffolding in the datapath. In the gate demo, two controllers move markers across a split-screen world through the real input path, and every one of 1,202 displayed frames matched the independent C++ reference over every byte. A randomized 10,000-frame soak (two and a half hours of simulated play) passed 205,026 checks with zero failures.

Composing the blocks revealed defects no per-block test could see: a data seam that silently carried only an eighth of each memory write, while its test compared exactly the eighth that survived; a deadlock between two blocks that are each correct alone; and both framebuffers sharing one DRAM bank, which shredded the display under real combined traffic. All three are fixed and pinned by tests.

The gate also measured that copying a finished frame off the machine for checking takes just under two frames, so debug captures run at 30 Hz. That number is the cost of the debug copy, not how fast the console draws.

Phases

The charter defines fourteen phases, each with a hard acceptance gate. Two have written, ratified gate reports.

0Board truth: probe the real hardware before freezing any numberBLOCKED · no board
1Specification, Nanquan IR and oracle skeleton6 of 7 gates green
2Console shell: the composed machine, double-buffered scanoutGATED · 2026-08-16
3Software console and minimal Nanquan languagein progress
4First exact tile and triangle: the hardware rasterizer beginsRTL simulated, not synthesized
5Opaque textured 3D coreRTL simulated, not synthesized
6Static Mantle and visible surface damagemost blocks verified in simulation
7Live terrain deformationthree blocks verified in simulation
8Geometry frontend and The Measurethree blocks verified in simulation
9Creatures and Transform Loomanimation path verified in simulation
10Myriad polygon stormtwo blocks verified in simulation
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 (a tiny program in the console's own language parses, type-checks and compiles) was deferred into Phase 3, in writing, and Phase 3 is now underway. The amber marks on Phases 6 to 10 mean blocks belonging to that phase are verified in simulation, and nothing more: none of those phases has a written gate report, and no phase above 2 has been closed.

Tests and proofs

The fast gate every change must pass currently runs 214 tests with zero failures, having risen from 180 over one day of work. Beyond it: long randomized nightly soaks, dedicated memory and video lanes, and 216 compiler test cases. Excluding generated code, the hardware description is about 29,900 lines of SystemVerilog, beside 27,800 lines of TypeScript, 23,700 lines of C++ reference, runtime and tools, and 46,900 lines of C++ tests.

Ten properties are registered for formal proof, which is mathematical model-checking rather than testing. One of them is worth retelling. The memory arbiter's fairness bound was frozen at 40 cycles and cited as evidence for a full development wave, but the proof had never actually been run. The moment it was made real, 40 failed. The bound was re-derived from the actual burst arithmetic and then proven exactly tight: the proof fails at 51 cycles and passes at 52, so 52 is the true worst case rather than a number that happened to pass. The old formula stays in the spec, struck through, so the error remains visible. One property, the memory-refresh bound, is not established: its full proof does not finish on this machine, so it is excluded from the automated lane and recorded as unfinished.

First synthesis

Synthesis now reaches the console itself, one block at a time. Thirteen of the shell's eighteen blocks have been placed and routed on a stand-in Cyclone V part, and they come to 4,096 ALMs between them, roughly a tenth of that device. The largest single block measured is the command scheduler at 815; the smallest is the video mode decoder at 70. Five blocks did not fit and are recorded as failures rather than estimated: four ran past their time budget and one was stopped after asking for 28 GB of memory on a machine with 24.

That last number has since moved. Composing the whole shell used to commit 28.4 GB against 24 GB of memory and thrash at near-zero processor use until it was killed, so there was no composed run at all. Two staging memories were restructured, and analysis and synthesis of the composed shell now runs to completion and stops with a precise complaint instead: the toolchain names two memories it could not turn into RAM blocks, because both are read asynchronously, and reports that the registers left over exceed the device. One of the two was not known to have that defect until the tool said so. There is still no composed resource figure and none is guessed at, but the obstacle is now a named fault with a route through it rather than a machine that could not hold the problem.

No block is marked SYNTHESIZED. The device is a stand-in until a board is probed, every input and output in these runs is virtual, and a per-block fit says nothing about how the composed machine would route or close timing. There is no timing-closure data and no bandwidth data; those directories exist and are empty on purpose.

05 · 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.

And what simply is not built yet

06 · 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 circuit is written: an executable reference implementation, directed tests, randomized tests that compare the circuit against that reference, and formal proofs where a property can be stated. The command format is generated from a single definition into C++, TypeScript and SystemVerilog, and the three are held byte-identical by a test that fails on any drift. There is no floating-point math anywhere in the render path; the software renderer is integer-only precisely so hardware can match it exactly.

The part that is easy to get wrong

Comparing two implementations catches disagreement. It cannot catch a mistake they share. Adversarial review rounds, reading the code against the frozen specification, have repeatedly found real bugs while the entire test suite was green:

A 65,536× amplification in a curve instruction. Both the C++ and TypeScript interpreters made the identical mistake, so the test comparing them agreed perfectly, on the wrong answer.

An invisible sun. A brightness conversion overflowed so that full brightness wrapped around to zero: any sun above a third of maximum energy drew exactly nothing.

A sky that silently did not exist. A shift bug collapsed all 48 columns of the sky cylinder to a single angle, turning every one of its 1,536 triangles into zero-area slivers the rasterizer dropped. The tests counted primitives and checked coordinates; none of them ever asked whether a pixel had been drawn. The fix ships with an assertion that does: zero sky pixels before, 371 after.

White that resolved to magenta. The display dither adds a small offset before quantizing each colour channel, and for green the offset can push full brightness one step past the top of its 6-bit field, which wraps to zero. The comment in the code proved the overflow impossible for red and blue and assumed green behaved the same; green's dither is twice as strong and its headroom half. Nothing in the project had ever produced saturated pure white until the new sun did, and the sun promptly resolved to a white-and-magenta checkerboard. The clamp is now law and a test drives every dither phase of every near-white green.

Formal proofs that had never run. Two blocks carried "formally proven" badges resting on proofs the tool had never once elaborated; they passed vacuously. When the lane was made real, both failed immediately, and fixing one of them surfaced a genuine hole in the memory protection. The rule that came out of it: a proof may not be cited as evidence unless the tool has recorded a passing run in which the assertions were demonstrably reachable.

Breaking the hardware on purpose

Every new block now ships with a mutation sweep: the circuit is deliberately broken in a list of specific ways, and the test suite has to notice each one. A test that stays green while the hardware is wrong is not evidence, and the only way to find that out is to make the hardware wrong. The sweep has caught something in most of the blocks built most recently, and in every case the test had been passing.

The sweep also keeps catching prose rather than circuitry. Four claims written into comments about why something mattered turned out to be false when the matching mutation changed nothing: a division that never truncates, a shift whose signedness makes no difference at these widths, a rounding order that is exactly equal except at the extremes, and a guard against a hazard that was not there. None of the four changed the hardware, and all four would have misled whoever read the file next.

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