| Filename | Latest commit message | Latest commit date |
|---|---|---|
Re-landed from PR #678 (branch wai/quantum-landscape-capacity, head |
||
| .. | ||
| measurements | ||
| rtl | ||
| scripts | ||
| src | ||
| tb | ||
| Cargo.lock | ||
| Cargo.toml | ||
| landscape.tcl | ||
| PROVENANCE.md | ||
| README.md | ||
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.
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
# 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 inKNOWN_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, samplebit-exact, under the set of that day. The 13 settle cases (the one with fields of 2^40 included) and the 42 chains of thechainscases, bit for bit, each answer also held to the job bycheck_settleandcheck_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 sobit-exactwithin it (above). - Cut points and draws: the 650
cellscut 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 200andjoint(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.