open-standards/wai/wai-quantum-landscape-fpga
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Transaction Science c0ba8a0ce5 wai-quantum: a landscape member declares its capacity, and is held to everything within it
Re-landed from PR #678 (branch wai/quantum-landscape-capacity, head
72829ec4c) as one commit on main, under the land-it model. Six review
rounds; round 6's 13 findings (1 medium) are fixed, each with a test that
fails on 1efbe6653.

- A member (CPU reference, WebGPU/CUDA/Metal, FPGA model) declares fixed
  capacity numbers; Capacity::admits is a pure function of the job,
  schedule and numbers; the host declines exactly those jobs; certify runs
  every vector and battery workload within capacity and grants bit-exact
  or statistical within it, stated as member.capacity in answers, labels
  and refusals; a floor below which certify refuses.
- The demotion store: a certification's probe is the rewrite itself (a
  sticky folder, chflags uchg/uappnd, chattr +i grade Uncertified); a busy
  lock records a demotion durably instead of dropping it; temp files
  matched exactly; probes sized to the largest rewrite; links followed one
  at a time with O_NOFOLLOW and refused in world-writable sticky folders;
  hard links refused; stale probes removed.
- Generation: long-moving settles (11u-59) and late two-cycles generated;
  the "longest settle" claim dropped; battery calls of 63 down to 17
  chains. The statistical grade's power is stated in §7.2 and in every
  statistical label.
- The audit's threads: capped over the process, lowered in priority, and
  held by a CI test that four mutants fail.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 15:49:07 -05:00
..
measurements wai-quantum-landscape-fpga: held to the grown conformance set, and what it cannot hold said 2026-11-07 03:36:31 -05:00
rtl wai-quantum-landscape-fpga: the review of #677 answered 2026-11-07 03:22:22 -05:00
scripts wai-quantum-landscape-fpga: the review of #677 answered 2026-11-07 03:22:22 -05:00
src wai-quantum: a landscape member declares its capacity, and is held to everything within it 2026-11-09 15:49:07 -05:00
tb wai-quantum-landscape-fpga: the review of #677 answered 2026-11-07 03:22:22 -05:00
Cargo.lock wai-quantum 0.9.0: two breaks since 0.8.1 raise its 0.y, a job is Send again, and every pin the version moves 2026-11-07 12:44:24 -05:00
Cargo.toml wai-quantum 0.9.0: two breaks since 0.8.1 raise its 0.y, a job is Send again, and every pin the version moves 2026-11-07 12:44:24 -05:00
landscape.tcl wai-quantum-landscape-fpga: the review of #677 answered 2026-11-07 03:22:22 -05:00
PROVENANCE.md wai-quantum-landscape-fpga: PROVENANCE names the copyright holder, Open Interface Engineering, Inc. 2026-11-07 03:49:23 -05:00
README.md wai-quantum: a landscape member declares its capacity, and is held to everything within it 2026-11-09 15:49:07 -05:00

wai-quantum-landscape-fpga

An FPGA board as a member of wai.quantum.landscape/1: 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. 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.

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

# 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:

./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:

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.

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.