Some checks failed
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 35s
wai-quantum wasmQ / does this change touch it (push) Successful in 19s
wai-quantum energy-landscape jobs / does this change touch it (push) Successful in 22s
WAI operation catalogue / does this change touch it (push) Successful in 24s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 44s
wai-app and Create / wai-app · does this change touch it (push) Successful in 37s
wai-fleet service / does this change touch it (push) Successful in 29s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 39s
wai-audit collector and player integration / does this change touch it (push) Successful in 51s
wai-quantum other formats / does this change touch it (push) Successful in 25s
wai-quantum for JavaScript / does this change touch it (push) Successful in 24s
wai-quantum editors / does this change touch it (push) Successful in 33s
wai-quantum wasmQ / the reference modules rebuild to their modules of record · linux (push) Successful in 39s
wai-quantum-landscape-fpga reference design / does this change touch it (push) Successful in 25s
wai-quantum contract / does this change touch it (push) Successful in 1m16s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 1m29s
wai-ledger service / does this change touch it (push) Successful in 1m17s
wai-quantum clippy / does this change touch it (push) Successful in 1m18s
wai-quantum resource estimation / does this change touch it (push) Successful in 45s
wai-quantum command line / does this change touch it (push) Successful in 1m16s
wai-quantum for Python / does this change touch it (push) Successful in 21s
wai-quantum docs, course and locales / does this change touch it (push) Successful in 1m17s
wai-quantum error correction and mitigation / does this change touch it (push) Successful in 23s
wai-quantum pulse and dynamics / does this change touch it (push) Successful in 19s
wai-quantum OpenQASM reader / does this change touch it (push) Successful in 35s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 44s
wai-quantum qudit jobs / does this change touch it (push) Successful in 20s
wai-sink-android native sink library / does this change touch it (push) Successful in 37s
WAI Studio / does this change touch it (push) Successful in 37s
wai-quantum verdicts and repairs / does this change touch it (push) Successful in 1m7s
wai-quantum views in agent hosts / does this change touch it (push) Successful in 1m5s
wai-quantum extension points / does this change touch it (push) Successful in 1m16s
wai-sink-swift native sink package / does this change touch it (push) Successful in 59s
wai-quantum studio and desktop / does this change touch it (push) Successful in 1m14s
wai-quantum variational and algorithm library / does this change touch it (push) Successful in 1m7s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 2m7s
wai-quantum-landscape-fpga reference design / the board's code on the board's processor's architecture · linux-arm64, release (push) Successful in 1m43s
wai-quantum service / does this change touch it (push) Successful in 1m11s
wai-fleet service / service · linux (push) Successful in 2m53s
wai-quantum wasmQ / interpreter and vectors under wasm32-wasip2 (wasmtime) (push) Successful in 3m56s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 4m9s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Failing after 5m15s
wai-quantum wasmQ / profile, engines, reference modules, adversarial, vectors · linux (push) Successful in 4m38s
wai-quantum pulse and dynamics / the same bits · linux-arm64 (push) Successful in 3m39s
wai-quantum other formats / the set under wasm32-wasip2 (wasmtime) (push) Successful in 3m47s
wai-audit collector and player integration / collector · linux (push) Successful in 4m48s
wai-quantum service / service energy · linux packages (push) Successful in 3m48s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux-arm64, release (push) Successful in 4m11s
wai-quantum contract / the policy is linked, and the deprecations page reads (push) Successful in 17s
wai-quantum-landscape-fpga reference design / the member against the model of the logic, and the logic against the model · linux, release (push) Failing after 2m54s
wai-quantum energy-landscape jobs / native against WebAssembly, over many seeds (push) Successful in 6m46s
wai-quantum other formats / readers, writers and the set · linux (push) Successful in 5m14s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 5m6s
wai-quantum editors / editors · linux (push) Successful in 6m35s
wai-quantum for JavaScript / javascript · linux (push) Successful in 4m20s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 7m34s
wai-ledger service / service · linux (push) Successful in 3m29s
wai-quantum extension points / the registry and the example · linux-arm64 (push) Successful in 7m40s
wai-quantum docs, course and locales / the Studio's words in every locale · linux (push) Successful in 20s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 10m58s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 11m25s
wai-quantum pulse and dynamics / reader, simulator and pulse jobs under wasm32-wasip2 (wasmtime) (push) Successful in 1m0s
wai-quantum error correction and mitigation / codes, decoders, texts and mitigation · linux, release (push) Successful in 4m21s
wai-quantum contract / API compatibility · linux (push) Successful in 8m58s
wai-quantum studio and desktop / desktop app · windows (push) Successful in 10m55s
wai-quantum clippy / clippy, warnings denied, every feature set · linux (push) Successful in 6m15s
wai-quantum qudit jobs / the module and the conformance set under wasm32-wasip2 (wasmtime) (push) Successful in 1m13s
wai-quantum for Python / python · linux (push) Successful in 6m5s
wai-quantum OpenQASM reader / corpus under wasm32-wasip2 (wasmtime) (push) Successful in 1m25s
wai-quantum OpenQASM reader / reader and corpus · linux (push) Successful in 2m38s
wai-quantum contract / schemas and the committed tool lists · linux (push) Successful in 10m27s
wai-quantum resource estimation / estimation and calibration · linux, release (push) Successful in 7m40s
wai-quantum pulse and dynamics / reader, simulator and pulse jobs · linux, release (push) Successful in 4m27s
wai-quantum verdicts and repairs / the intent set and the verdict under wasm32-wasip2 (wasmtime) (push) Successful in 1m38s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 2m8s
wai-quantum docs, course and locales / the course and the command line's languages · linux (push) Successful in 7m14s
wai-quantum verdicts and repairs / verdicts, repairs and the intent set · linux (push) Successful in 2m25s
wai-quantum energy-landscape jobs / the same bits · linux-arm64 (push) Successful in 17m4s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux, release (push) Successful in 2m31s
wai-quantum extension points / the registry under wasm32-wasip2 (wasmtime) (push) Successful in 2m32s
wai-quantum energy-landscape jobs / reader, kernels, answers and the stated result · linux, release (push) Successful in 19m19s
wai-quantum views in agent hosts / views · the page and its engine (push) Successful in 4m31s
wai-quantum extension points / the registry and the example · linux (push) Successful in 4m49s
wai-quantum energy-landscape jobs / the module under wasm32-wasip2 (wasmtime) (push) Successful in 21m33s
wai-quantum qudit jobs / the module, the job, the conformance set, the referee and the verb · linux (push) Successful in 10m48s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 7m17s
wai-quantum studio and desktop / studio · browser (push) Successful in 8m21s
wai-quantum views in agent hosts / views · a scripted host (push) Successful in 11m44s
wai-quantum wasmQ / profile, engines, reference modules, adversarial, vectors · macos (push) Successful in 2m4s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 47s
wai-quantum docs, course and locales / every command and snippet in the docs · linux (push) Successful in 22m55s
wai-fleet service / service · macos (push) Successful in 38s
wai-quantum service / service · linux (push) Successful in 17m40s
wai-quantum energy-landscape jobs / the same bits · macos (push) Successful in 7m26s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 7m25s
wai-quantum editors / editors · macos (push) Successful in 1m41s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 2m8s
wai-ledger service / service · macos (push) Successful in 1m28s
WAI Studio / Studio in WebKit · linux (push) Successful in 27m7s
wai-quantum command line / command line · macos (push) Successful in 3m1s
wai-quantum command line / command line · linux (push) Successful in 34m5s
wai-quantum for Python / python · macos (push) Successful in 3m21s
wai-quantum pulse and dynamics / the same bits · macos (push) Successful in 1m54s
wai-quantum qudit jobs / the same bits · macos (push) Successful in 2m25s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 16m12s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 7m24s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 4m51s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 10m55s
wai-quantum service / service · macos (push) Successful in 7m15s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Successful in 15m21s
The second version of the energy-landscape job (extensions/quantum-landscape-2.md), alongside the first, which answers byte for byte as before (register T1-1 and T4-5; holes H-7, H-9, R-30). - Each variable has its own cost, of either sign (`cost I C`; `cost C` may be below 0). A variable whose cost is below 0 is never 0 in a state of lowest energy, so `exactly K I J ...` (one-hot and cardinality) and `atmost 1 I J ...` lower exactly to couplings of -1 and whole-number fields; `#! check exactly` and `#! check atmost` grade an answer. - Kernels wai.quantum.landscape2/settle and /sample: each cell at its own cost; the settle move is the cell's lowest energy, a tie to 0 then to +, the first version's move wherever a cost is 0 or more. A request names the version with "protocol"; none is the first. - Joules: a run at temperature 0 priced at about 0.88 pJ an accumulate, the open FPGA member's measured settle figure, with its provenance, caveats and files under `evidence`; a run above it is "unpriced", with why and no figure. The first version keeps its rates (its replay lines hash whole answers) and its section 4.4 now says what stands behind them. - Ten worked examples: colouring (four regions, an odd ring of five, six regions, and four regions that all touch, which must fail), assignment at 3x3, 4x4 and 5x5, two in each row and column, four queens, and the assignment at temperature 0. Every lowest state solves each one marked as working where all arrangements are checked; seeds 1 to 100 at its own plan all solve it, and the two that must fail fail at all 100. The set states each one's reach. - The vector rule: quantum-conformance/landscape/vectors.json is frozen as it stood on 2026-11-10 and v2/vectors.json at publication, each BLAKE3 pinned (conformance2); the first version's section 9 states how its set changed on 11-06 and 11-09, and why. - Members: Member::protocols (provided; the first version by default). Only the CPU reference runs /2; the WebGPU, CUDA, Metal and FPGA members are certified on /1 alone and passed over for a /2 job, saying why. The FPGA member's label names the frozen /1 set. - Reach: the CLI (--protocol 2), the job service's /v1/landscape routes (stamped with the answer's version), Python (protocol=), npm and the desktop app carry /2; verify holds a /2 answer to its form. The MCP tool keeps to /1 (its output schema is landscape.v1.json) and a client sends no /2 job to a provider, whose description offers /1. - Registry: the two kernel labels; schemas/landscape.v2.json. Workflow pins updated. Co-Authored-By: Claude Opus 5.5 <[email protected]>
453 lines
33 KiB
Markdown
453 lines
33 KiB
Markdown
# wai-quantum-landscape-fpga
|
||
|
||
An FPGA board as a member of [`wai.quantum.landscape/1`](../extensions/quantum-landscape.md): the
|
||
two kernels of the energy-landscape job, the settle sweep and the sampling sweep, in programmable
|
||
logic, with the host that runs jobs on the board, holds it to the conformance set and meters its
|
||
power. It is a reference design: the logic, the host, the scripts and the measurements are here,
|
||
under this repository's Apache-2.0.
|
||
|
||
It is classical throughout. The cells are three-state variables in logic, the randomness is a
|
||
counter-based generator, and nothing here is a quantum effect. Every answer says so.
|
||
|
||
## The board
|
||
|
||
| | |
|
||
|---|---|
|
||
| board | KV260 starter kit, `SK-KV260-G` (a K26 module on a carrier) |
|
||
| programmable logic | `xck26-sfvc784-2LV-c`; board part `xilinx.com:kv260_som:part0:2.0` |
|
||
| processor running the host | the module's four Cortex-A53 cores, Ubuntu 24.04 from a microSD card (kernel `6.8.0-*-xilinx`) |
|
||
| power sensor read | the carrier's INA260 (`hwmon` name `ina260_u14`), 10 mW steps: the board's input, so every regulator's loss is inside every figure. A sample is one read of its register, once a second, not an average over the second. |
|
||
| clock | the processing system's `pl_clk0`, 100 MHz |
|
||
| link to the processor | the AXI link (the default build): AXI-Lite registers on the processing system's master port `M_AXI_HPM0_FPD` at `0xA000_0000`, bulk loads read by DMA through its high-performance port `S_AXI_HP0_FPD`, and a chain sequencer in the logic; or, in the EMIO build, 95 EMIO lines of its GPIO controller. Both through `/dev/mem`. |
|
||
| the two ports' widths | 128 bits, as this board's boot leaves them; the FPGA manager does not change them, so the AXI build meets them with width converters and writes no processor register. `ps-ports` reads them. |
|
||
|
||
The register map is `src/regs.rs` (the engine) and `src/axi.rs` (the link). Nothing else is
|
||
board-specific except `landscape.tcl` (part and board part), `src/axi.rs` and `src/emio.rs` (the
|
||
link's address, the GPIO controller's), and `src/board.rs` (the FPGA manager, the clock register,
|
||
the sensor's name).
|
||
|
||
## What it does
|
||
|
||
One engine (`rtl/landscape_core.v`) holds up to 64 cells. Each cell's field
|
||
`h_i = b_i + Σ_j W_ij s_j` is computed every clock by its own adder cone. The couplings `W` (−1, 0,
|
||
+1), the fields `b` (20 bits signed), the cost `c` (20 bits) and the cut-point table are registers
|
||
the host writes.
|
||
|
||
| mode | what it is |
|
||
|---|---|
|
||
| T0 | the settle kernel, §4.1: every cell at once to `threshold(h, c)`, one sweep a clock, stopping at a fixed point, a two-cycle or the bound. Whole-number arithmetic. |
|
||
| GIBBS | the sample kernel, §4.2: one cell at a time in order, each drawn given the cells before it have just moved. Four clocks a cell. |
|
||
| JACOBI | every cell drawn from the same old state and moved together. **Not** a kernel of the spec: it does not sample the joint distribution. It is here only as the negative control the spec's coupled test must catch. |
|
||
|
||
**The draws are the reference's.** The draw of cell `i` in sweep `k` of a chain with seed `C` is the
|
||
top 53 bits of `out(C, k·n + i + 1) = mix(C + (k·n + i + 1)·0x9E3779B97F4A7C15)`. The index rises by
|
||
one per cell, so the engine keeps a counter, adds the constant once a cell, and runs SplitMix64's
|
||
`mix` in a nine-stage pipeline (two 64-bit products in sixteen-bit limbs, 20 DSP slices). The host
|
||
writes the counter's start, `C + 0x9E3779B97F4A7C15`.
|
||
|
||
**The cut points come from the host.** For each sweep's temperature the host computes
|
||
`(a, b) = cuts(h, c, 1/T)` with the reference's own function, so the logic needs no exponential; a
|
||
cell returns −1 if `u < a`, 0 if `u < b`, else +1. The table holds 8,192 pairs of 54 bits, as
|
||
`2^(13−WBITS)` temperature levels of `2^WBITS` fields from `WIN_LO`. A field outside the window takes
|
||
the threshold, which is the reference's draw only where that draw is certain, so the host places the
|
||
window over every field a cell can reach whose draw is not certain at some temperature of the run,
|
||
and refuses a job for which no window fits. A run longer than the table goes in segments: the host
|
||
rewrites the table and starts every chain on the next segment from where it ended, its counter at
|
||
the draw the reference takes next.
|
||
|
||
**The member.** `src/member.rs` implements `wai_quantum::quantum_landscape::member::Member`. It is
|
||
named `fpga:kv260-landscape-64`. It says nothing of its own parity: the host certifies it
|
||
(`member::certify`), running the conformance set's `settle` and `chains` cases on the device and
|
||
granting `bit-exact` to a kernel only if every case within its declared capacity comes back bit for
|
||
bit, and every case the host generates for it: by rule, every cell count to 64 (each power of two,
|
||
one less and one more), every call size to 32 chains, each band of a wavefront and a whole batch, a row of 63 couplings, fields
|
||
at the wrap points of 16-bit lanes, a run past draw index 2²⁴, and a corner with every number at its
|
||
most; and at each number it declares, a wave down 64 cells, fields at ±524,287 and a cost of 1,048,575
|
||
drawn hot, cold and hot then at 0, and two fields spanning 8,192 drawn at temperature 8,192. For a
|
||
sampling kernel that misses, the host tries the statistical battery within it. After that, the host
|
||
audits its answers (§7.3 of the extension) and demotes it at the first that differs. This host keeps
|
||
the demotion in `~/.local/state/wai-quantum-landscape-fpga/demotions.json` (or the file
|
||
`WAI_LANDSCAPE_DEMOTIONS` names), under the board's id and its engine's ID and version, so a demoted
|
||
board is not certified again on its next job; a new engine is. It reads the file when it certifies
|
||
the board, after it holds the board's lock, with every demotion kept in a file of its own beside it,
|
||
and rewrites it as a demotion would; it keeps a demotion at once in a file of its own and merges it
|
||
into the file under the file's own lock, waited for at most 5 s (where that runs out, the demotion
|
||
stays in its own file, which every certification reads), so jobs and hosts sharing it keep one
|
||
another's; a link is followed to its target, except a link in a sticky folder anyone may write.
|
||
Where the file cannot be read, parsed or rewritten (a full disk, a read-only folder, a file another
|
||
user owns in a sticky folder, one marked immutable), it has a second name (a hard link), the lock is
|
||
held past 5 s, or `HOME` is unset and no file is named (an empty `WAI_LANDSCAPE_DEMOTIONS` names
|
||
none), it says so on standard error and grades no kernel `bit-exact`.
|
||
|
||
**Its declared capacity** (`member::CAPACITY`, §5 of the extension): 64 cells; above temperature 0,
|
||
fields to ±524,287 and a cost to 1,048,575, its 20-bit registers, and uncertain fields within a span
|
||
of 8,192 at a temperature, its table's window. At temperature 0 it takes any field (below). The host
|
||
runs it on no job past those numbers, and a test holds the declared numbers to the engine's own
|
||
limits, case by case over the whole set and the generated cases
|
||
(`the_declared_capacity_is_exactly_what_the_engine_holds`), and on 3,000 random workloads and
|
||
schedules (`the_capacity_and_the_engine_agree_on_random_jobs`).
|
||
|
||
**What it cannot hold.** The conformance set holds cases past all three of its limits (settle
|
||
cases of 65 to 1,000 cells, chains of 128 and 1,024 cells, sampled fields up to 2^31 − 1, a battery
|
||
workload whose uncertain fields span 135,000). Those are past its declared capacity, so the host
|
||
neither runs them on it nor counts them against it: on the register-level model, which the
|
||
testbenches hold the logic to, the host certifies both kernels `bit-exact` within that capacity
|
||
against the set as it stands (`the_model_is_certified_bit_exact_within_its_declared_capacity`), and
|
||
every job's answer states the capacity. The board itself was certified `bit-exact` for both kernels
|
||
under the set of 2026-11-06 (`vectors.json` sha256 `43e543a634e0ffdc...`), before the set grew;
|
||
the measurements below were taken then, and it is to be certified again on the set as it stands.
|
||
That set is frozen since 2026-11-10 (sha256 `1da13d2570eb5b13...`, BLAKE3 `e629cc914e93d257...`,
|
||
`conformance2::VECTORS_1_BLAKE3`, which a test holds the embedded file to), and every answer's label
|
||
names it as the set the member is graded on.
|
||
|
||
**The first version only.** The engine reads one cost for every cell, so it runs the kernels of
|
||
`wai.quantum.landscape/1` and not those of `wai.quantum.landscape/2`, whose cells each have a cost of
|
||
their own ([quantum-landscape-2](../extensions/quantum-landscape-2.md) §5). The host gives it no job of
|
||
the second version, placed or asked for by name, and passes it over for one, saying why. A test holds
|
||
that (`the_board_is_graded_on_the_frozen_first_set_and_runs_no_second_version_job`). It runs whatever schedule the job gives, one
|
||
temperature a sweep: a run of sweeps above 0 as sampled segments, a run at 0 in T0 mode, sweep `k`
|
||
drawing from `k·n + 1` whatever came before it. The reader, the truth, the energy, the verdict, the
|
||
checks on what a member returns, the replay digest and the seal stay on the host
|
||
(`wai_quantum::quantum_landscape`), so a job's answer on the board is the spec's answer, with
|
||
`member` naming the board and the receipt naming the key that sealed it.
|
||
|
||
**Large fields at temperature 0.** The spec allows fields and costs far beyond the engine's 20-bit
|
||
registers. At temperature 0 a cell compares its coupling sum `S`, which lies in `[−d, d]` (`d` its
|
||
couplings, at most 63), with two thresholds, `c − b` and `−c − b`; only where a threshold falls in
|
||
`[−d − 1, d + 1]` does its exact place matter. So for sweeps at 0 the host maps a field or cost too
|
||
large for the engine to one that leaves every comparison the same, and the settle is the same bit
|
||
for bit (`member::lowered_settle`; the conformance set's `fields-at-the-bound` case, fields of 2^40,
|
||
reaches three of its five cases, and the crate's tests hold all five, with 300 random workloads of
|
||
fields and costs up to 2^40, to the reference's settle). Sampling with such fields is refused.
|
||
|
||
**Limits, and what the host refuses with a sentence:** more than 64 variables; sampling with a field
|
||
beyond ±524,287 or a cost beyond 1,048,575; a job whose uncertain fields span more than 8,192.
|
||
|
||
## The link
|
||
|
||
**AXI-Lite registers** (`rtl/landscape_axi.v`). Word `k` of the engine's map (`src/regs.rs`) is at
|
||
`0xA000_0000 + 4k`; the link's own registers start at word `0x8000` (`src/axi.rs`).
|
||
|
||
**Bulk loads by DMA.** The host writes a list of 64-bit entries, `{address, value}`, into locked
|
||
pages, cleans them from its caches to DDR (the high-performance port is not coherent), and gives the
|
||
link the page's physical address; the link reads the list in 16-beat bursts and applies one entry
|
||
every two clocks. Couplings, fields, a whole cut-point table and a batch's chain starts all go this
|
||
way. A read error stops the applying and is reported; the list is still drained.
|
||
|
||
**The anneal loop in fabric.** A batch of up to 256 chains goes into the link's chain RAM (each
|
||
chain's start and draw counter); the sequencer writes each into the engine, starts it, waits for it,
|
||
and copies its state, sweeps and status into the result RAM, chain after chain, with nothing from
|
||
the processor between them. The host fills the chain RAM by DMA.
|
||
|
||
**Results by DMA.** A write master on the same port sends the result RAM to DDR, 32 bytes a chain,
|
||
128 chains a page. The host fills the page with all ones first (the illegal cell pair), cleans and
|
||
invalidates it before the write-back and invalidates it after, because the port is not coherent. An
|
||
error is reported, the write-back's done count proves it ran, and a result word still all ones
|
||
proves it did not write there.
|
||
|
||
**Safe on a shared board.** A processor access to a logic address that nothing answers never
|
||
completes, and it holds the board until someone power-cycles it; another session may replace the
|
||
design between two accesses. So the link puts a constant signature on eight EMIO lines, and the host
|
||
reads it, through the GPIO controller, before every AXI access: a GPIO read always answers, so a
|
||
replaced design is caught before an AXI access is made. And the link answers every access within a
|
||
few clocks whatever the engine is doing: an engine access while the DMA or the sequencer holds the
|
||
engine is dropped and counted, never queued, and the host checks the count after every batch.
|
||
|
||
**One session at a time, and a reload seen.** Every board command takes one lock before it touches
|
||
the board, `load` included, so a reload waits for the job that holds the board. Before a job, the
|
||
host looks for another session by name (this tool, its two scripts, the vendor's loader, and what a
|
||
site names in `WAI_QUANTUM_LANDSCAPE_FPGA_FOREIGN`). And a reload that comes anyway is seen: a
|
||
reload of the same design passes the signature and the engine's ID, but it starts the engine's
|
||
uptime counter again and resets its BOUND register, so the host checks both before it takes a
|
||
workload or a table as already loaded and after every job, and refuses a result read across a
|
||
reload instead of keeping it.
|
||
|
||
**The EMIO build** keeps the first link: every register access a handshake over the GPIO lines. The
|
||
multi-core build has only that link.
|
||
|
||
## What it costs to build
|
||
|
||
| item | note |
|
||
|---|---|
|
||
| the board kit | `SK-KV260-G`, with its power supply. Its price is the vendor's list price; this README does not quote one. |
|
||
| a microSD card | 16 GB or more, with the board's Ubuntu 24.04 image |
|
||
| a network connection | the host is reached over SSH |
|
||
| the vendor's FPGA tool | Vivado 2026.1, which this design was built and measured with. Its no-charge licence tier covers this part. |
|
||
| a Linux x86-64 machine for that tool | ours had 15 GB of memory; the tool reported a peak of 5.1 GB for the AXI build |
|
||
| a Rust toolchain | the repository's pinned version (`rust-toolchain.toml`), on an aarch64 Linux machine, or with the `aarch64-unknown-linux-gnu` target and `aarch64-linux-gnu-gcc` to cross-compile |
|
||
|
||
The design uses no licensed IP: its vendor blocks are the processing system's configuration
|
||
(`zynq_ultra_ps_e` 3.5) and, in the AXI build, the tool's AXI width and protocol converters; the
|
||
build checks out only the tool's own synthesis and implementation features.
|
||
|
||
## Build and load
|
||
|
||
```sh
|
||
# 1. the bitstream, on the tool's machine, in an empty directory: [MHz] [cores] [axi|emio]
|
||
sh wai-quantum-landscape-fpga/scripts/build_bitstream.sh # the AXI build -> landscape.bit.bin, and its sha256
|
||
# 2. the host, for the board's processor
|
||
sh wai-quantum-landscape-fpga/scripts/build_host.sh # -> wai-quantum-landscape-fpga (aarch64)
|
||
# 3. on the board, as root
|
||
./wai-quantum-landscape-fpga load landscape.bit.bin # proves the load by its kernel-log line, then reads the engine's ID
|
||
./wai-quantum-landscape-fpga status # every board command finds the loaded link by its signature
|
||
```
|
||
|
||
If the FPGA manager's `flags` file reads non-zero (this board boots with `0x20`), a plain
|
||
bitstream load needs it set to 0 first; `scripts/run_all.sh` does that and puts it back.
|
||
|
||
**No bitstream is distributed here.** The tool's licence (Vivado 2026.1's end-user licence agreement,
|
||
§3.1(3)C) allows a bitstream holding the vendor's components to be distributed "only for use to
|
||
program a Xilinx Device", and §4(a) grants no right to use one for other devices. That is a
|
||
field-of-use condition this repository's Apache-2.0 does not carry, so the build steps are given
|
||
instead. The bitstreams measured below had sha256
|
||
`bb82505526e33403073924373a64d3519394344e9bf6d0193634c157e42b6dfb` (the AXI build, as these steps
|
||
make it), `3d84dbd355c9552c7593d3d5aa823284a4941acd25e5f3b7c92d5c0bc36ee441` (the first AXI build,
|
||
before the result write-back) and `feea5015b2f8d85049fd48bcc66493a411d551326365f67b1db2ae65ec00074c`
|
||
(the EMIO build); whether a rebuild gives the same bytes has not been checked.
|
||
|
||
## Run the checks
|
||
|
||
On the board, as root:
|
||
|
||
```sh
|
||
./wai-quantum-landscape-fpga conform # the conformance set, the battery and its control, the jobs, random settles, field read-back
|
||
./wai-quantum-landscape-fpga agree --seeds 200 # every worked example, board against the CPU reference
|
||
./wai-quantum-landscape-fpga joint # joint statistics against exact enumeration, at the registered seed
|
||
./wai-quantum-landscape-fpga latency # processor-to-logic latency of the loaded link
|
||
./wai-quantum-landscape-fpga throughput
|
||
echo '{"program": "vars 2\noppose 0 1\nsolve\n", "seed": "7"}' | ./wai-quantum-landscape-fpga landscape run -
|
||
```
|
||
|
||
`landscape` takes the spec's JSON request and prints its answer, sealed with the key in
|
||
`$WAI_QUANTUM_KEY` or `~/.config/wai/quantum-key` (made on first use; `--demo-key` and `--unsealed`
|
||
as for `wai-quantum`). Anywhere, with no board:
|
||
|
||
```sh
|
||
cargo run --release -- selftest # every check against the register-level model (src/model.rs)
|
||
cargo run --release -- joint --model # the joint-statistics test on the model
|
||
cargo run --release -- landscape run req.json --model --demo-key # a job on the model, certified and sealed
|
||
sh scripts/run_tb.sh # both testbenches against the logic, in Icarus Verilog
|
||
```
|
||
|
||
The testbenches (plain Verilog) replay a session recorded from the model (`gen-tb`) against the
|
||
logic and compare every read with the model's. `tb/tb_landscape.v` goes through the EMIO port and
|
||
its synchroniser.
|
||
`tb/tb_landscape_axi.v` drives the AXI link: register accesses over AXI-Lite, every bulk load and
|
||
every batch's chain starts through the DMA from a memory model that answers with irregular delays
|
||
and several bursts outstanding, every batch through the sequencer with its results read from the
|
||
result RAM and then written back to the memory model, and in every batch an engine read that must
|
||
come back at once, dropped and counted. `scripts/run_tb.sh` records both scripts and runs both in
|
||
Icarus Verilog, and CI runs it on every change: the EMIO script, 174,345 lines and 2,668 checked
|
||
reads, and the AXI script, 175,830 lines, 171 write lists, 50 batches and 4,464 checked values, both
|
||
with 0 mismatches, and the vendor's simulator (xsim 2026.1; the command is in the script) gives the
|
||
same on the same scripts (`measurements/2026-11-06/build.txt`). A flipped expectation is reported.
|
||
|
||
`scripts/run_all.sh` does all of it on the board in order, then the power campaign.
|
||
|
||
## The power campaign
|
||
|
||
`scripts/power_campaign.sh`, by the rules that follow from a whole-board sensor:
|
||
|
||
- **Run minus halt on one bitstream**: no reload between the two states, so placement and clock tree
|
||
cancel and the difference is the job's dynamic power.
|
||
- **Randomised order** of run and halt in every pass, so drift cannot alias onto it.
|
||
- **Sampling on the board, in the measuring process**, nothing else running: the processor is inside
|
||
the measurement, so any login or file copy during sampling moves the number.
|
||
- **Reload detection at every point**: the engine's uptime counter must keep counting, its ID must
|
||
read back, and the kernel log's count of FPGA writes must not move. A flagged pass is dropped.
|
||
- **Statistic**: the mean of per-pass differences of medians; error, the standard error across passes.
|
||
- **Controls**: a run/halt step response for the settle time (`scripts/step_settle.py`), a null
|
||
(halt against halt), the same job at half the clock (dynamic power must halve), and a known-answer
|
||
vehicle where one exists. The vehicle used here is a design of the maintainers' own, not in this
|
||
repository; a builder's own known answer goes in `KNOWN_ANSWER`.
|
||
- **A prediction is committed before each campaign** (`measurements/*/PREDICTION*.txt`).
|
||
|
||
## What it measured
|
||
|
||
Every figure: **board-level, one board, one meter, this design.** The sensor reads the whole board
|
||
at its input (about 3.1 W idle), so the power figures are differences of a few percent of the total,
|
||
regulator losses included. The figures in nJ and pJ are derived, not read: a mean power divided by
|
||
the rate the engine's own counters give.
|
||
|
||
The build sizes, the tool and block versions, the bitstreams' sha256 and the simulator results are
|
||
in `measurements/2026-11-06/build.txt`.
|
||
|
||
### The builds
|
||
|
||
| build | LUTs | registers | block RAM | UltraRAM | DSP | worst slack at 100 MHz |
|
||
|---|---|---|---|---|---|---|
|
||
| AXI link, one engine (the default) | 28,145 (24.0%) | 13,773 | 4 | 4 | 20 | +0.993 ns |
|
||
| EMIO link, one engine | 25,539 (21.8%) | 11,121 | 0 | 4 | 20 | +0.294 ns |
|
||
| EMIO link, four engines | 80,927 (69.1%; 95% of slices) | 44,355 | 0 | 16 | 80 | +0.441 ns |
|
||
|
||
### Checks on the board (2026-11-06)
|
||
|
||
On the final AXI build and the first AXI build, the same results on each; the conformance and
|
||
agreement checks also on the EMIO build, with the same results (`measurements/2026-11-06/`):
|
||
|
||
- **Certified by the host: settle `bit-exact`, sample `bit-exact`, under the set of that day.** The
|
||
13 settle cases (the one with fields of 2^40 included) and the 42 chains of the `chains` cases,
|
||
bit for bit, each answer also held to the job by `check_settle` and `check_chain`. Under the set
|
||
as it stands now, on the model, which the testbenches hold the logic to: every case within its
|
||
declared capacity bit for bit, and so `bit-exact` within it (above).
|
||
- **Cut points and draws:** the 650 `cells` cut points the host computes equal the set's; 29 of
|
||
those cells drawn 100,000 times each on the device, every draw the reference's and every frequency
|
||
within 4 standard errors.
|
||
- **The statistical battery:** all 8 workloads pass (20,000 chains each against the exact
|
||
distribution), and the all-at-once control fails 7 of them.
|
||
- **Jobs:** 31 of 31 requests answer as the set says, refusals included. The 13 worked examples that
|
||
run, over 200 seeds each, at their own temperature and at 0, answer as the CPU reference does (26
|
||
of 26, `agree`), and the one written to be refused is refused. 2,000 random settles up to 64 cells are the reference's; 4,096 fields read back right.
|
||
- **Joint statistics against exact enumeration** (pre-registered, `measurements/2026-11-06/
|
||
JOINT-PREREG.txt`, seed 2027, through the sequencer): every case passes every rule.
|
||
|
||
| case | chains | bins | chi-square p | G p | cell p | pair p | control chi-square |
|
||
|---|---|---|---|---|---|---|---|
|
||
| ring 8, fixed T = 1.5, 4 sweeps | 200,000 | 2,930 | 0.095 | 0.037 | 1.000 | 1.000 | 244,079 on 3,247 bins, p < 1e-300 |
|
||
| ring 8, the job's anneal from T = 2.5, 10 sweeps | 200,000 | 45 | 0.708 | 0.702 | 1.000 | 1.000 | 1,933,336 on 181 bins, p < 1e-300 |
|
||
| dense 6, fixed T = 4, 2 sweeps | 200,000 | 729 | 0.884 | 0.870 | 1.000 | 0.047 | 91,995 on 729 bins, p < 1e-300 |
|
||
| sparse 10, the job's anneal from T = 1.5, 8 sweeps | 400,000 | 190 | 0.346 | 0.167 | 1.000 | 0.773 | 14,139,391 on 1,531 bins, p < 1e-300 |
|
||
|
||
Every chain's final state equals the reference's (1,000,000 of 1,000,000), so this holds the
|
||
shared kernel to its exact law on the board's own outputs. The control has the exact marginals and
|
||
independent cells: its per-cell test passes (p 1.000 in every case) while its joint test fails by
|
||
hundreds of standard errors, which is what per-cell statistics alone would miss.
|
||
|
||
### Processor-to-logic latency (`latency`, the AXI build; the EMIO lines on the EMIO build)
|
||
|
||
Each access timed alone with the processor's generic timer (10 ns a tick), `n` times; the median,
|
||
the 99th percentile and the largest seen (`measurements/2026-11-06/axi-final/latency-axi.txt`,
|
||
`emio-checks/latency-emio.txt`).
|
||
|
||
| access | n | median | p99 | largest |
|
||
|---|---|---|---|---|
|
||
| EMIO register read, handshake over GPIO (EMIO build) | 5,000 | 930 ns | 940 ns | 104 µs |
|
||
| EMIO register write, handshake over GPIO (EMIO build) | 5,000 | 790 ns | 800 ns | 30.5 µs |
|
||
| a GPIO register read (the AXI link's signature) | 100,000 | 230 ns | 240 ns | 89.9 µs |
|
||
| AXI-Lite read, a link register / an engine register | 100,000 | 230 / 240 ns | 230 / 240 ns | 19.9 / 25.7 µs |
|
||
| AXI-Lite write, to its response (store, then DSB) | 100,000 | 250 ns | 380 ns | 56.8 µs |
|
||
| a guarded read, as every job access goes (signature, then read) | 100,000 | 470 ns | 470 ns | 21.5 µs |
|
||
| a write list by DMA, 1 entry, kick to done | 5,000 | 7.6 µs | 9.3 µs | 25.8 µs |
|
||
| a write list by DMA, 512 entries (one page), kick to done | 2,000 | 20.3 µs | 23.5 µs | 54.7 µs |
|
||
| a batch's results by DMA, 128 chains (one page), kick to read | 2,000 | 37.6 µs | 47.9 µs | 49.7 µs |
|
||
|
||
Posted AXI-Lite writes, back to back with nothing between them, came to 51 ns each: one loop of
|
||
100,000 timed whole, a mean. A DMA list's entries cost (20.3 − 7.6) µs / 511, about 25 ns each,
|
||
beyond the fixed cost of a kick; whole, a 512-entry list comes to 39.7 ns an entry.
|
||
|
||
End to end, each timed once over the whole run (the table loads: the median of 6):
|
||
|
||
| | EMIO, register accesses | AXI, register accesses | AXI, DMA, sequencer and write-back |
|
||
|---|---|---|---|
|
||
| a full cut-point table (64 levels × 128 fields, 32,835 writes) | 29.0 ms | 17.5 ms | 3.81 ms |
|
||
| chains of 8 cells × 4 sweeps | 14.6 µs a chain (4,096) | 9.4 µs a chain (16,384) | 4.34 µs a chain (65,536; 230,000 a second) |
|
||
|
||
Of a chain's 4.34 µs, the engine is busy for 139 clocks, 1.39 µs, by its own count (the crate's
|
||
tests hold the model to that count, and the testbenches hold the logic to the model). The
|
||
sequencer's state machine adds about 33 clocks around it (`rtl/landscape_axi.v`: 12 to write the
|
||
chain in, 18 to copy its result out, and the start). The rest is the host's lists, kicks and polls,
|
||
and the DMA's transfers.
|
||
|
||
### Power
|
||
|
||
Each class by run minus halt on one bitstream, the pre-registered statistic, all predictions committed
|
||
first (`measurements/2026-11-06/`). The classes ran on the EMIO build (12:39 to 14:55 UTC); the AXI
|
||
builds, with the same engine, were checked against them.
|
||
|
||
| measurement | build | result | prediction | |
|
||
|---|---|---|---|---|
|
||
| settle, dense 64-cell recall, 12.5 M settles a second | EMIO | 162.0 ± 3.3 mW (10 passes): 13.0 nJ a settle, 0.88 pJ an accumulate | 160 (130 to 200) | within |
|
||
| sequential sampling, 64 cells, 24.9 M cell draws a second | EMIO | 11.0 ± 1.8 mW (10 passes): 0.44 ± 0.07 nJ a cell draw, within the instrument's floor, not relied on | 12 (4 to 30) | within; see the null |
|
||
| the idle loop, control and counters only | EMIO | 1.9 ± 4.0 mW (8 passes) | 4 (0 to 12) | within |
|
||
| the settle job at 50 MHz | EMIO | 80.0 ± 2.6 mW: 0.494 ± 0.019 of 100 MHz | 0.5 within 2 SE | passes |
|
||
| null, halt against halt | EMIO | 8.1 ± 1.9 mW (8 passes) | \|Δ\| < 2 SE | **fails** |
|
||
| null, repeated as pre-registered (`NULL-REPEAT.txt`) | AXI, first | −1.9 ± 2.7 mW (8 passes) | \|Δ\| < 2 SE | passes |
|
||
| known-answer vehicle (the 2026-09 design), at 08:11 UTC, before the class campaign | its own | 73.8 ± 10.3 mW (8 passes; one pass's own spread 29 mW) | 61.7 ± 6.5, within 2 combined σ | passes, weakly: 37 to 86 mW would have passed |
|
||
| settle job, the AXI build's check | AXI, first | 158.3 ± 4.4 mW (3 passes) | the EMIO result within 2 combined SE | passes |
|
||
| settle job, the final build's check | AXI, final | 166.7 ± 6.7 mW (3 passes) | the EMIO result within 2 combined SE | passes |
|
||
|
||
The board drew about 3.14 W halted and 3.30 W running the settle job. **One of the two nulls failed**
|
||
(8.1 ± 1.9 mW, no job in either slot, and not following which slot came first), so the instrument's
|
||
floor is not shown to be below about 8 mW: the sampling and idle figures, 11 and 2 mW, are within it
|
||
and are reported, not relied on. The settle figures are twenty times that floor. The class campaign's
|
||
first attempt (08:01 UTC) measured the step and the known answer, then stopped: the session check
|
||
mistook its own parent process for another session; that is fixed (`src/board.rs`). The class
|
||
campaign itself ran without the known answer, so its own instrument checks are the null that failed
|
||
and the clock-scaling check that passed. During it a status read over SSH touched the board once, at
|
||
about 13:25 UTC, during the settle class.
|
||
|
||
**The step response** (`emio-classes/step.dat`, 300 s each way, no settle): the level is flat from
|
||
the first 15 seconds on, 3,300 mW running and 3,140 mW halted (`step_settle.py`: every 15-sample
|
||
median within 20 mW of the final level), so the 60 s settle is enough. Single samples stray all
|
||
through both segments, the last 299 s in: spikes of the sensor or the processor, which the
|
||
statistic's medians do not follow.
|
||
|
||
|
||
**The first version of this design** (2026-11-05, fields of 11 bits, a 128-bit xoroshiro register for
|
||
draws of its own, 16-bit cut points; sampling `statistical`, not bit-exact) measured, with its
|
||
known-answer control at 70.4 ± 7.5 mW against 61.7 ± 6.5 expected and a null of −1.2 ± 2.3 mW
|
||
(`measurements/2026-11-05/`):
|
||
|
||
| job | one core | four cores |
|
||
|---|---|---|
|
||
| settle, dense 64-cell recall | 146.7 ± 2.0 mW: 11.7 nJ a settle, about 0.80 pJ an accumulate | 585.8 ± 1.8 mW, 3.99 ± 0.06 times one core |
|
||
| sequential sampling, 8 sweeps at T = 0.25 | 6.7 ± 1.8 mW: 0.20 ± 0.05 nJ a cell draw | 42.1 ± 1.9 mW: 0.32 ± 0.01 nJ a cell draw, 2.0 σ from the one-core figure |
|
||
|
||
Neither is the 0.16 pJ an accumulate of the spec's price table, which comes from a different design
|
||
with its couplings fixed in the logic. The second version of the protocol prices a run at temperature 0
|
||
at this member's settle figure, about 0.88 pJ an accumulate, with this section as its evidence, and
|
||
prices no run above it: the sampling figure above is not relied on ([quantum-landscape-2](../extensions/quantum-landscape-2.md) §4.3).
|
||
|
||
## What is not done, and what came out worse
|
||
|
||
- **Bitstream reproducibility** is unchecked, and no bitstream is published (above).
|
||
- **No hardware-in-the-loop CI.** The board has a lock between jobs (`src/board.rs`) but no
|
||
networked power relay, so a runner cannot power-cycle it.
|
||
- **One board, one meter.** No second unit has repeated these numbers, and the sensor reads the whole
|
||
board, not the logic's supply.
|
||
- **Four cores fit, only on the EMIO link, and the host does not use them as four.** It addresses one
|
||
core at a time or all at once (the same job on each). The AXI link drives one engine.
|
||
- **The guard leaves a window.** The host reads the signature and then makes the AXI access, usually
|
||
under half a microsecond later; the longest gap seen was 21.5 µs, and preemption can stretch it
|
||
without bound. A reload by another session landing inside that window would still hang the board.
|
||
The lock keeps this tool's own sessions out of it; another tool's reload is not excluded.
|
||
- **The DMA's pages are locked, not pinned.** The kernel may still move a locked page (compaction and
|
||
contiguous allocation do). The host reads each page's physical address before every transfer and
|
||
again after, and refuses the transfer if it moved, but a page moved during a write-back has had its
|
||
4 KiB written where the page used to be. A buffer the kernel owns (a reserved region, a CMA or
|
||
dma-heap buffer behind a driver) would rule this out; this host has none.
|
||
- **The port widths are the boot's.** The build assumes the processing system's master port and its
|
||
high-performance port at 128 bits, as this board's boot sets them; the host reads
|
||
both and refuses otherwise, and does not set them. Another boot image may need another build.
|
||
- **Certified within a capacity, not beyond it.** 64 cells, sampled fields of ±524,287 and a cost of
|
||
1,048,575, and a window of 8,192 fields a temperature: its grade holds within those, and the host
|
||
runs it on no job past them. A host offered other members gives such a job to the next; this
|
||
board's own host, with one member, refuses it, saying which number it is past. At temperature 0 large fields are mapped exactly; more cells
|
||
would need the engine to tile its cells, wider sampling fields wider registers, and a wider spread
|
||
of uncertain fields more than one window.
|
||
- **The joint test's histogram is the reference's.** The board is bit-exact, so the test holds the
|
||
shared kernel to exact enumeration on the board's outputs, not an independent sampler.
|
||
- **A cell update takes four clocks**, and in sequential mode one of 64 cones is used at a time.
|
||
- **The AXI build reports one critical warning**: a false-path constraint written for the EMIO
|
||
port's synchroniser finds no such cell in it. It constrains nothing there.
|
||
- **A rebuild is not checked against the measured one.** The build stops on a tool other than
|
||
2026.1 and takes each block at the version it was measured with, but whether the same steps give
|
||
the same bytes is unknown. A rebuild with a different sha256 is accepted by its own checks on the
|
||
board: `conform`, `agree --seeds 200` and `joint` (seed 2027), reported with the tool version and
|
||
the sha256.
|
||
|
||
## Files
|
||
|
||
| | |
|
||
|---|---|
|
||
| `rtl/landscape_core.v` | the engine: cones, the T0, GIBBS and JACOBI modes, the draw pipeline, the table, counters |
|
||
| `rtl/landscape_axi.v`, `rtl/landscape_top_axi.v` | the AXI link (AXI-Lite port, write-list DMA, chain sequencer) and its top level |
|
||
| `rtl/landscape_port.v`, `rtl/landscape_top.v` | the EMIO port and its top level |
|
||
| `rtl/landscape_multi.v`, `rtl/landscape_top_multi.v` | several engines behind one EMIO port |
|
||
| `landscape.tcl`, `rtl/landscape.xdc` | the build (`LINK axi` or `emio`), and the constraints on the EMIO crossing |
|
||
| `src/` | the host: register maps, the links (`axi.rs`, `emio.rs`), driver, member, model, checks, the joint-statistics test (`exact.rs`), latency (`latency.rs`), power meter, command line |
|
||
| `tb/tb_landscape.v`, `tb/tb_landscape_axi.v` | the testbenches, for the EMIO port and for the AXI link; `scripts/run_tb.sh` runs both |
|
||
| `scripts/` | build, run-everything, power campaign, and the two analysis scripts |
|
||
| `measurements/` | each campaign's prediction and raw logs |
|
||
|
||
Where the design came from: [PROVENANCE.md](PROVENANCE.md).
|