From 2d03db0bfb099f365bf08b92ed946a699b1bd7d5 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Fri, 6 Nov 2026 03:01:36 -0500 Subject: [PATCH 01/11] wai-quantum-fpga: an FPGA board as a member of wai.quantum.landscape/1, and its power prediction The engine (rtl/) runs the settle and sample kernels of the energy-landscape job for up to 64 three-state cells in programmable logic. Couplings, fields, the cost and the cut-point table are registers the host writes. The sample kernel takes the reference's draws: the engine counts chain + j * 0x9E3779B97F4A7C15 and runs SplitMix64's mix in a nine-stage pipeline, and compares the top 53 bits with cut points the host computes with the reference's cuts(), so the logic needs no exponential. A field outside the table's window takes the threshold, and the host puts the window over every field whose draw is not certain, or refuses the job. The host (src/) implements wai_quantum::quantum_landscape::member::Member for the board, earns its parity by running the conformance set's settle and chains vectors on the device at start, and answers jobs through job::call_on, sealed. src/model.rs models the logic at the register port; the testbench (tb/) replays a session recorded from it against the RTL and compares every read. On the board at 100 MHz: the settle and chains vectors bit for bit, so both kernels are bit-exact; the cell and coupled frequency tests with every draw equal to the reference's; the jobs vectors 14/14; every worked example over 200 seeds at its own temperature and at 0, answer for answer identical to the CPU reference. measurements/2026-11-06/PREDICTION.txt is committed before the power measurement it predicts. Co-Authored-By: Claude Opus 5.5 --- wai/wai-quantum-fpga/Cargo.lock | 459 ++++++++++++++++++ wai/wai-quantum-fpga/Cargo.toml | 19 + wai/wai-quantum-fpga/landscape.tcl | 58 +++ .../measurements/2026-11-06/PREDICTION.txt | 25 + wai/wai-quantum-fpga/rtl/landscape.xdc | 5 + wai/wai-quantum-fpga/rtl/landscape_core.v | 456 +++++++++++++++++ wai/wai-quantum-fpga/rtl/landscape_multi.v | 51 ++ wai/wai-quantum-fpga/rtl/landscape_port.v | 66 +++ wai/wai-quantum-fpga/rtl/landscape_top.v | 9 + .../rtl/landscape_top_multi.v | 8 + .../scripts/power_campaign.sh | 42 ++ wai/wai-quantum-fpga/scripts/power_tables.py | 32 ++ wai/wai-quantum-fpga/scripts/run_all.sh | 28 ++ wai/wai-quantum-fpga/scripts/step_settle.py | 25 + wai/wai-quantum-fpga/src/board.rs | 120 +++++ wai/wai-quantum-fpga/src/conform.rs | 361 ++++++++++++++ wai/wai-quantum-fpga/src/emio.rs | 164 +++++++ wai/wai-quantum-fpga/src/engine.rs | 335 +++++++++++++ wai/wai-quantum-fpga/src/lib.rs | 20 + wai/wai-quantum-fpga/src/main.rs | 337 +++++++++++++ wai/wai-quantum-fpga/src/member.rs | 248 ++++++++++ wai/wai-quantum-fpga/src/model.rs | 379 +++++++++++++++ wai/wai-quantum-fpga/src/port.rs | 76 +++ wai/wai-quantum-fpga/src/power.rs | 242 +++++++++ wai/wai-quantum-fpga/src/regs.rs | 69 +++ wai/wai-quantum-fpga/src/script.rs | 50 ++ wai/wai-quantum-fpga/tb/tb_landscape.v | 80 +++ 27 files changed, 3764 insertions(+) create mode 100644 wai/wai-quantum-fpga/Cargo.lock create mode 100644 wai/wai-quantum-fpga/Cargo.toml create mode 100644 wai/wai-quantum-fpga/landscape.tcl create mode 100644 wai/wai-quantum-fpga/measurements/2026-11-06/PREDICTION.txt create mode 100644 wai/wai-quantum-fpga/rtl/landscape.xdc create mode 100644 wai/wai-quantum-fpga/rtl/landscape_core.v create mode 100644 wai/wai-quantum-fpga/rtl/landscape_multi.v create mode 100644 wai/wai-quantum-fpga/rtl/landscape_port.v create mode 100644 wai/wai-quantum-fpga/rtl/landscape_top.v create mode 100644 wai/wai-quantum-fpga/rtl/landscape_top_multi.v create mode 100755 wai/wai-quantum-fpga/scripts/power_campaign.sh create mode 100644 wai/wai-quantum-fpga/scripts/power_tables.py create mode 100755 wai/wai-quantum-fpga/scripts/run_all.sh create mode 100755 wai/wai-quantum-fpga/scripts/step_settle.py create mode 100644 wai/wai-quantum-fpga/src/board.rs create mode 100644 wai/wai-quantum-fpga/src/conform.rs create mode 100644 wai/wai-quantum-fpga/src/emio.rs create mode 100644 wai/wai-quantum-fpga/src/engine.rs create mode 100644 wai/wai-quantum-fpga/src/lib.rs create mode 100644 wai/wai-quantum-fpga/src/main.rs create mode 100644 wai/wai-quantum-fpga/src/member.rs create mode 100644 wai/wai-quantum-fpga/src/model.rs create mode 100644 wai/wai-quantum-fpga/src/port.rs create mode 100644 wai/wai-quantum-fpga/src/power.rs create mode 100644 wai/wai-quantum-fpga/src/regs.rs create mode 100644 wai/wai-quantum-fpga/src/script.rs create mode 100644 wai/wai-quantum-fpga/tb/tb_landscape.v diff --git a/wai/wai-quantum-fpga/Cargo.lock b/wai/wai-quantum-fpga/Cargo.lock new file mode 100644 index 000000000..02f66be38 --- /dev/null +++ b/wai/wai-quantum-fpga/Cargo.lock @@ -0,0 +1,459 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "blake3" +version = "1.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae" +dependencies = [ + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.1", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cc" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375989decec1a0fa424442bed520fa12ede43a48475ce8c7417d2c69b32b5f6b" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "libc" +version = "0.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "foldhash", + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wai-quantum" +version = "0.8.1" +dependencies = [ + "blake3", + "ed25519-dalek", + "serde", + "serde_json", +] + +[[package]] +name = "wai-quantum-fpga" +version = "0.1.0" +dependencies = [ + "blake3", + "serde_json", + "wai-quantum", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/wai/wai-quantum-fpga/Cargo.toml b/wai/wai-quantum-fpga/Cargo.toml new file mode 100644 index 000000000..e598ee580 --- /dev/null +++ b/wai/wai-quantum-fpga/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "wai-quantum-fpga" +version = "0.1.0" +edition = "2024" +rust-version = "1.85" +license = "Apache-2.0" +publish = false +description = "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, the register-level model the logic is tested against, and the host that runs jobs on the board, holds it to the conformance set and meters its power." +repository = "https://git.transaction.science/Transaction-Science/open-standards" + +[dependencies] +# The job form, the reader, the reference kernels, the answer and its seal: the board is a member, +# so everything but the kernels stays in the reference crate. +wai-quantum = { version = "0.8.1", path = "../wai-quantum", default-features = false, features = ["quantum_landscape", "quantum_engine", "quantum_receipt"] } +serde_json = { version = "1", features = ["preserve_order"] } +blake3 = "1" + +[profile.release] +opt-level = 3 diff --git a/wai/wai-quantum-fpga/landscape.tcl b/wai/wai-quantum-fpga/landscape.tcl new file mode 100644 index 000000000..3c4cece14 --- /dev/null +++ b/wai/wai-quantum-fpga/landscape.tcl @@ -0,0 +1,58 @@ +# Bitstream build for the energy-landscape engine. Vivado batch mode: +# vivado -mode batch -source landscape.tcl -tclargs [FREQ ] [TAG ] [JOBS ] [CORES ] +# The part and board are data: PART and BOARD below. The processing system provides the clock and the +# EMIO lines; there is no other programmable-logic I/O. +set part xck26-sfvc784-2LV-c +set board xilinx.com:kv260_som:part0:2.0 +proc arg {name dflt} { + global argv + set i [lsearch $argv $name] + if {$i >= 0 && [llength $argv] > $i + 1} { return [lindex $argv [expr {$i + 1}]] } + return $dflt +} +set freq [arg FREQ 100] +set tag [arg TAG landscape] +set jobs [arg JOBS 4] +set cores [arg CORES 1] +set here [file dirname [file normalize [info script]]] +puts "BUILD: tag=$tag freq=$freq jobs=$jobs cores=$cores here=$here" +create_project ev_$tag ./ev_$tag -part $part -force +set_property board_part $board [current_project] + +create_bd_design sys +create_bd_cell -type ip -vlnv [lindex [get_ipdefs *zynq_ultra_ps_e*] end] ps +apply_bd_automation -rule xilinx.com:bd_rule:zynq_ultra_ps_e -config {apply_board_preset 1} [get_bd_cells ps] +set_property -dict [list \ + CONFIG.PSU__FPGA_PL0_ENABLE {1} \ + CONFIG.PSU__CRL_APB__PL0_REF_CTRL__FREQMHZ $freq \ + CONFIG.PSU__USE__M_AXI_GP0 {0} CONFIG.PSU__USE__M_AXI_GP1 {0} CONFIG.PSU__USE__M_AXI_GP2 {0} \ + CONFIG.PSU__GPIO_EMIO__PERIPHERAL__ENABLE {1} \ + CONFIG.PSU__GPIO_EMIO__PERIPHERAL__IO {95} \ +] [get_bd_cells ps] +create_bd_port -dir O -from 94 -to 0 emio_o +create_bd_port -dir I -from 94 -to 0 emio_i +connect_bd_net [get_bd_pins ps/emio_gpio_o] [get_bd_ports emio_o] +connect_bd_net [get_bd_pins ps/emio_gpio_i] [get_bd_ports emio_i] +create_bd_port -dir O -type clk pl_clk0 +connect_bd_net [get_bd_pins ps/pl_clk0] [get_bd_ports pl_clk0] +validate_bd_design +make_wrapper -files [get_files sys.bd] -top -import +generate_target all [get_files sys.bd] + +if {$cores > 1} { + add_files [list $here/rtl/landscape_top_multi.v $here/rtl/landscape_multi.v $here/rtl/landscape_core.v] + set_property verilog_define [list LANDSCAPE_K=$cores] [current_fileset] +} else { + add_files [list $here/rtl/landscape_top.v $here/rtl/landscape_port.v $here/rtl/landscape_core.v] +} +add_files -fileset constrs_1 $here/rtl/landscape.xdc +set_property top design_top [current_fileset] +update_compile_order -fileset sources_1 + +launch_runs impl_1 -to_step write_bitstream -jobs $jobs +wait_on_run impl_1 +puts "IMPL_STATUS: [get_property STATUS [get_runs impl_1]] PROGRESS: [get_property PROGRESS [get_runs impl_1]]" +open_run impl_1 +report_utilization -file ev_$tag.util.rpt +report_timing_summary -max_paths 5 -file ev_$tag.timing.rpt +puts "BITSTREAM: [glob -nocomplain ./ev_$tag/ev_$tag.runs/impl_1/*.bit]" diff --git a/wai/wai-quantum-fpga/measurements/2026-11-06/PREDICTION.txt b/wai/wai-quantum-fpga/measurements/2026-11-06/PREDICTION.txt new file mode 100644 index 000000000..4a5259fdd --- /dev/null +++ b/wai/wai-quantum-fpga/measurements/2026-11-06/PREDICTION.txt @@ -0,0 +1,25 @@ +Written 2026-11-06, before this design (rtl version 2) was metered on the board, and committed +before the measurement began. Not edited after the data. + +WHAT IS MEASURED. Run minus halt on one bitstream, whole-board power sensor, 100 MHz, by the +protocol in scripts/power_campaign.sh: no reload between the two states, run/halt order randomised +in each pass, 60 s settle and 30 samples at 1 Hz read in the measuring process with nothing else +running, every point checked for a reload (the engine's uptime counter, its ID, the kernel log's +count of FPGA writes), statistic the mean of per-pass differences of medians, error the standard +error across passes. Label: board-level, one board, one meter, this design. + +WHAT CHANGED SINCE THE LAST MEASUREMENT (rtl version 1, 2026-11-05, measurements/2026-11-05): +fields and the cost widen from 11 and 10 bits to 20; a cell update takes four clocks, not three; +the sampler's draw is a SplitMix64 pipeline (two 64-bit products, 20 DSP slices) instead of a +128-bit xoroshiro register; the table entry is 108 bits, not 34. + +PREDICTIONS, point and range: + t0-recall 160 mW (130..200) version 1 measured 146.7 +/- 2.0; the wider field adders and + comparators add switching when a field changes sign + gibbs-recall 12 mW (4..30) version 1 measured 6.7 +/- 1.8 (one core), 10.5 +/- 0.5 a core + (four-core build); fewer updates a second (25 M, not 33 M) but + the multipliers and a wider table read on each + t0-idle 4 mW (0..12) control and counters only, as version 1 (3.8 +/- 1.6) + null 0 mW, pass if |delta| < 2 SE + t0-recall at 50 MHz: half the 100 MHz value, pass if the ratio is within 2 SE of 0.5 + known answer (the 2026-09 vehicle, where present): 61.7 mW, pass if within 2 combined sigma diff --git a/wai/wai-quantum-fpga/rtl/landscape.xdc b/wai/wai-quantum-fpga/rtl/landscape.xdc new file mode 100644 index 000000000..06866056e --- /dev/null +++ b/wai/wai-quantum-fpga/rtl/landscape.xdc @@ -0,0 +1,5 @@ +# The EMIO lines cross from the PS's bus clock into pl_clk0. req is synchronised in edge_emio.v; addr, +# we and wdata are stable before req is seen; rdata is stable before ack toggles. +set_false_path -to [get_cells -hierarchical -filter {NAME =~ *req_s1_reg*}] +set_false_path -through [get_pins -hierarchical -filter {NAME =~ *PS8_i/EMIOGPIOO*}] +set_false_path -through [get_pins -hierarchical -filter {NAME =~ *PS8_i/EMIOGPIOI*}] diff --git a/wai/wai-quantum-fpga/rtl/landscape_core.v b/wai/wai-quantum-fpga/rtl/landscape_core.v new file mode 100644 index 000000000..5c104fc69 --- /dev/null +++ b/wai/wai-quantum-fpga/rtl/landscape_core.v @@ -0,0 +1,456 @@ +// Energy-landscape engine: the two kernels of wai.quantum.landscape/1 (extensions/quantum-landscape.md +// section 4) for up to 64 cells, in programmable logic. A classical design: three-state cells, a +// counter-based random stream, no quantum effect. +// +// One field cone per cell computes h_i = b_i + sum_j W_ij s_j from the current state, every clock. +// The couplings W (ternary), the fields b and the cost c are registers the host writes. +// +// MODE 0 T0 every cell at once to threshold(h, c): the settle kernel, section 4.1. One sweep +// per clock; it stops at a fixed point, a two-cycle or the bound. +// MODE 1 GIBBS one cell at a time, in order, each drawn given the cells before it have just +// moved: the sample kernel, section 4.2. Four clocks per cell. +// MODE 2 JACOBI every cell drawn from the same old state and moved together at the end of the +// sweep. NOT the sample kernel: it does not sample the joint distribution. It is +// here only as the negative control the spec's coupled test asks to fail. +// +// 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), +// out(s, j) = mix(s + j * 0x9E3779B97F4A7C15), the SplitMix64 output. Cells go in order and the +// index rises by one per cell, so the engine keeps a counter x, adds the constant once per cell and +// runs mix() in a nine-stage pipeline that advances once per cell; the host writes x's start +// (C + the constant) and the pipeline is primed for nine clocks when a job starts. +// +// The draw is compared with two cut points the host computes for the cell's field at the sweep's +// temperature (a = floor(P(-1) 2^53), b = floor((P(-1)+P(0)) 2^53), 54 bits each): -1 if u < a, +// 0 if u < b, else +1. The table holds 8,192 pairs: 2^(13-WBITS) temperature levels of 2^WBITS +// fields from WIN_LO. A field outside the window takes threshold(h, c); the host puts the window +// where every field whose draw is not certain falls inside it, or refuses the job. +// +// Register map: src/regs.rs. Cycle counts are part of the contract with src/model.rs. +`timescale 1ns/1ps +`default_nettype none + +module landscape_field ( + input wire [127:0] s, // {neg,pos} per cell + input wire [127:0] w, // {neg,pos} per coupling, row i + input wire signed [19:0] b, + output wire signed [21:0] h +); + wire [63:0] p, n; + genvar j; + generate + for (j = 0; j < 64; j = j + 1) begin : T + assign p[j] = (w[2*j] & s[2*j]) | (w[2*j+1] & s[2*j+1]); + assign n[j] = (w[2*j] & s[2*j+1]) | (w[2*j+1] & s[2*j]); + end + endgenerate + function automatic signed [2:0] c3(input [2:0] pp, input [2:0] nn); + c3 = $signed({2'b0, pp[0]}) + $signed({2'b0, pp[1]}) + $signed({2'b0, pp[2]}) + - $signed({2'b0, nn[0]}) - $signed({2'b0, nn[1]}) - $signed({2'b0, nn[2]}); + endfunction + wire signed [2:0] v0 [0:21]; + wire signed [3:0] v1 [0:10]; + wire signed [4:0] v2 [0:5]; + wire signed [5:0] v3 [0:2]; + wire signed [6:0] v4 [0:1]; + wire signed [7:0] v5; + genvar k; + generate + for (k = 0; k < 21; k = k + 1) begin : C + assign v0[k] = c3(p[3*k+2:3*k], n[3*k+2:3*k]); + end + assign v0[21] = c3({2'b0, p[63]}, {2'b0, n[63]}); + for (k = 0; k < 11; k = k + 1) begin : L1 + assign v1[k] = v0[2*k] + v0[2*k+1]; + end + for (k = 0; k < 5; k = k + 1) begin : L2 + assign v2[k] = v1[2*k] + v1[2*k+1]; + end + assign v2[5] = v1[10]; + for (k = 0; k < 3; k = k + 1) begin : L3 + assign v3[k] = v2[2*k] + v2[2*k+1]; + end + endgenerate + assign v4[0] = v3[0] + v3[1]; + assign v4[1] = v3[2]; + assign v5 = v4[0] + v4[1]; + assign h = v5 + b; +endmodule + +// The low 64 bits of x * K for a 64-bit constant K, in three stages, each stage's registers enabled +// by `en`: sixteen-bit limbs, the ten partial products that reach the low word, their sums by +// weight, then the total. +module landscape_mul64 #(parameter [63:0] K = 64'd0) ( + input wire clk, + input wire en, + input wire [63:0] x, + output reg [63:0] y = 64'd0 +); + wire [15:0] x0 = x[15:0], x1 = x[31:16], x2 = x[47:32], x3 = x[63:48]; + localparam [15:0] K0 = K[15:0], K1 = K[31:16], K2 = K[47:32], K3 = K[63:48]; + reg [31:0] p00 = 0, p01 = 0, p10 = 0, p02 = 0, p11 = 0, p20 = 0, p03 = 0, p12 = 0, p21 = 0, p30 = 0; + reg [33:0] w0 = 0, w1 = 0, w2 = 0, w3 = 0; + always @(posedge clk) if (en) begin + p00 <= x0 * K0; + p01 <= x0 * K1; p10 <= x1 * K0; + p02 <= x0 * K2; p11 <= x1 * K1; p20 <= x2 * K0; + p03 <= x0 * K3; p12 <= x1 * K2; p21 <= x2 * K1; p30 <= x3 * K0; + w0 <= {2'b0, p00}; + w1 <= {2'b0, p01} + {2'b0, p10}; + w2 <= {2'b0, p02} + {2'b0, p11} + {2'b0, p20}; + w3 <= {2'b0, p03} + {2'b0, p12} + {2'b0, p21} + {2'b0, p30}; + y <= {30'd0, w0} + {14'd0, w1, 16'd0} + {w2[31:0], 32'd0} + {w3[15:0], 48'd0}; + end +endmodule + +// SplitMix64's mix() on a counter: u = top 53 bits of mix(x), nine stages, advancing on `en`. +module landscape_draws ( + input wire clk, + input wire load, // x <- x0, the pipeline's contents become stale (prime it) + input wire [63:0] x0, + input wire en, + output wire [52:0] u +); + localparam [63:0] GOLDEN = 64'h9E3779B97F4A7C15; + reg [63:0] x = 64'd0, g1 = 64'd0, g3 = 64'd0, g5 = 64'd0; + wire [63:0] m1, m2; + always @(posedge clk) begin + if (load) x <= x0; + else if (en) x <= x + GOLDEN; + if (en) begin + g1 <= x ^ (x >> 30); + g3 <= m1 ^ (m1 >> 27); + g5 <= m2 ^ (m2 >> 31); + end + end + landscape_mul64 #(.K(64'hBF58476D1CE4E5B9)) M1 (.clk(clk), .en(en), .x(g1), .y(m1)); + landscape_mul64 #(.K(64'h94D049BB133111EB)) M2 (.clk(clk), .en(en), .x(g3), .y(m2)); + assign u = g5[63:11]; +endmodule + +module landscape_core ( + input wire clk, + input wire reg_stb, // one clock + input wire reg_we, + input wire [15:0] reg_addr, + input wire [31:0] reg_wdata, + output reg [31:0] reg_rdata, + output wire busy_o, + output wire done_o +); + localparam MODE_T0 = 2'd0, MODE_GIBBS = 2'd1, MODE_JACOBI = 2'd2; + localparam [3:0] S_IDLE = 0, S_LOAD = 1, S_T0 = 2, S_REQ = 3, S_TBL = 4, S_COMMIT = 5, + S_JAC = 6, S_FINAL = 7, S_END = 8, S_IDX = 9, S_PRIME = 10; + localparam [1:0] TERM_NONE = 0, TERM_FP = 1, TERM_2C = 2, TERM_BOUND = 3; + localparam integer PRIME = 9; // the draw pipeline's depth + + // ---- host-written configuration ------------------------------------------------------ + reg [1:0] mode = MODE_T0; + reg rep = 1'b0; + reg final_t0 = 1'b0; + reg [5:0] n_m1 = 6'd0; + reg [19:0] theta = 20'd0; + reg [31:0] bound = 32'd0; + reg [15:0] spl = 16'd1; // sweeps per temperature level + reg [5:0] levels_m1 = 6'd0; + reg [127:0] boundary = 128'd0; + reg [63:0] x0 = 64'd0; // the draw counter's start: chain seed + 0x9E3779B97F4A7C15 + reg signed [21:0] win_lo = 22'sd0; // the table window's first field + reg [3:0] wbits = 4'd7; // the window holds 2^wbits fields + + wire w_we = reg_stb & reg_we & (reg_addr[15:8] == 8'h10); + wire b_we = reg_stb & reg_we & (reg_addr[15:6] == 10'b0000_0010_00); + reg w_clear = 1'b0; + + // ---- state --------------------------------------------------------------------------- + reg [127:0] state = 128'd0; + reg [127:0] prev = 128'd0; + reg [127:0] prev2 = 128'd0; + reg have1 = 1'b0, have2 = 1'b0; + reg [3:0] fsm = S_IDLE; + reg done = 1'b0; + reg [1:0] term = TERM_NONE; + reg [5:0] sel = 6'd0; + reg [63:0] sel_hot = 64'd1; + reg [5:0] level = 6'd0; + reg [15:0] spl_cnt = 16'd0; + reg [31:0] steps = 32'd0; + reg [3:0] prime_cnt = 4'd0; + + assign busy_o = (fsm != S_IDLE); + assign done_o = done; + + // ---- the cones ----------------------------------------------------------------------- + wire signed [21:0] h [0:63]; + wire [63:0] gt, lt; + wire signed [21:0] th_s = {2'b00, theta}; + wire signed [21:0] nth_s = -th_s; + genvar i; + generate + for (i = 0; i < 64; i = i + 1) begin : CELL + reg [127:0] row = 128'd0; + reg signed [19:0] b = 20'sd0; + always @(posedge clk) begin + if (w_clear) row <= 128'd0; + else if (w_we && reg_addr[7:2] == i) begin + case (reg_addr[1:0]) + 2'd0: row[31:0] <= reg_wdata; + 2'd1: row[63:32] <= reg_wdata; + 2'd2: row[95:64] <= reg_wdata; + 2'd3: row[127:96] <= reg_wdata; + endcase + end + if (w_clear) b <= 20'sd0; + else if (b_we && reg_addr[5:0] == i) b <= reg_wdata[19:0]; + end + landscape_field f (.s(state), .w(row), .b(b), .h(h[i])); + assign gt[i] = (h[i] > th_s); + assign lt[i] = (h[i] < nth_s); + end + endgenerate + + wire signed [21:0] h_sel = h[sel]; + reg signed [21:0] h_reg = 22'sd0; + reg det_gt = 1'b0, det_lt = 1'b0, in_win = 1'b0; + reg [12:0] tidx = 13'd0; + + // ---- the cut-point table: 8,192 x {b[53:0], a[53:0]} ----------------------------------- + (* ram_style = "ultra" *) reg [107:0] tbl [0:8191]; + reg [12:0] tbl_addr = 13'd0; + reg [53:0] wa = 54'd0; // a, written first + reg [31:0] tbl_b_lo = 32'd0; // b's low word; b's high word commits the pair + reg [107:0] tbl_q = 108'd0; + wire tbl_host_we = reg_stb & reg_we & (reg_addr == 16'h001A); + wire tbl_run_rd = (fsm == S_TBL); + reg tbl_host_rd = 1'b0; + wire [12:0] tbl_a = tbl_run_rd ? tidx : tbl_addr; + always @(posedge clk) + if (tbl_run_rd | tbl_host_rd | tbl_host_we) begin + if (tbl_host_we) tbl[tbl_a] <= {reg_wdata[21:0], tbl_b_lo, wa}; + else tbl_q <= tbl[tbl_a]; + end + wire [53:0] cut_a = tbl_q[53:0]; + wire [53:0] cut_b = tbl_q[107:54]; + + // ---- the draws ------------------------------------------------------------------------- + wire sampling = (mode == MODE_GIBBS) | (mode == MODE_JACOBI); + wire draw_load = (fsm == S_LOAD) & sampling; + wire draw_adv = (fsm == S_PRIME) | (fsm == S_COMMIT); + wire [52:0] u; + landscape_draws D (.clk(clk), .load(draw_load), .x0(x0), .en(draw_adv), .u(u)); + + wire [54:0] uz = {2'b00, u}; + wire u_lt_a = (uz < {1'b0, cut_a}); + wire u_lt_b = (uz < {1'b0, cut_b}); + wire [1:0] s_cut = u_lt_a ? 2'b10 : (u_lt_b ? 2'b00 : 2'b01); + wire [1:0] s_smp = in_win ? s_cut : {det_lt, det_gt}; + + // ---- termination, on registers only ---------------------------------------------------- + wire eq1 = (state == prev); + wire eq2 = (state == prev2); + wire step_limit = (steps == bound); + wire last_cell = (sel == n_m1); + wire last_sweep = (steps + 32'd1 == bound); + + wire t0_terminal = (have1 & eq1) | (have2 & eq2) | step_limit; + wire t0_commit = ((fsm == S_T0) & ~t0_terminal) | (fsm == S_FINAL); + wire g_commit = (fsm == S_COMMIT) & (mode == MODE_GIBBS); + wire j_commit = (fsm == S_COMMIT) & (mode == MODE_JACOBI); + wire jac_all = (fsm == S_JAC); + wire load_state = (fsm == S_LOAD) | (reg_stb & reg_we & (reg_addr == 16'h0003) & reg_wdata[3] & (fsm == S_IDLE)); + + generate + for (i = 0; i < 64; i = i + 1) begin : UPD + always @(posedge clk) begin + if (load_state) state[2*i +: 2] <= boundary[2*i +: 2]; + else if (t0_commit) state[2*i +: 2] <= {lt[i], gt[i]}; + else if (jac_all) state[2*i +: 2] <= prev[2*i +: 2]; + else if (g_commit & sel_hot[i]) state[2*i +: 2] <= s_smp; + + if (load_state) prev[2*i +: 2] <= boundary[2*i +: 2]; + else if (t0_commit) prev[2*i +: 2] <= state[2*i +: 2]; + else if (j_commit & sel_hot[i]) prev[2*i +: 2] <= s_smp; + + if (t0_commit) prev2[2*i +: 2] <= prev[2*i +: 2]; + end + end + endgenerate + + // ---- counters ------------------------------------------------------------------------ + reg [47:0] uptime = 48'd0; + reg [47:0] cycles = 48'd0; + reg [47:0] updates = 48'd0; + reg [47:0] jobs = 48'd0; + reg [20:0] rd_hi = 21'd0; + + wire start = reg_stb & reg_we & (reg_addr == 16'h0003) & reg_wdata[0]; + wire cont = reg_stb & reg_we & (reg_addr == 16'h0003) & reg_wdata[1]; + wire stop = reg_stb & reg_we & (reg_addr == 16'h0003) & reg_wdata[2]; + wire clrw = reg_stb & reg_we & (reg_addr == 16'h0003) & reg_wdata[4]; + wire clrc = reg_stb & reg_we & (reg_addr == 16'h0003) & reg_wdata[5]; + + // the window offset and the level's base, for the table index + wire signed [22:0] d_full = {h_reg[21], h_reg} - {win_lo[21], win_lo}; + wire [13:0] wsize = 14'd1 << wbits; + wire [12:0] lbase = {7'd0, level} << wbits; + + always @(posedge clk) begin + uptime <= uptime + 48'd1; + w_clear <= clrw; + tbl_host_rd <= reg_stb & reg_we & (reg_addr == 16'h0016); + + if (clrc) begin + cycles <= 48'd0; updates <= 48'd0; jobs <= 48'd0; + end else begin + if (fsm != S_IDLE) cycles <= cycles + 48'd1; + if (fsm == S_COMMIT) updates <= updates + 48'd1; + if (fsm == S_END) jobs <= jobs + 48'd1; + end + + if (fsm == S_REQ || fsm == S_IDLE) h_reg <= h_sel; + if (fsm == S_IDX) begin + in_win <= (d_full >= 23'sd0) && (d_full < $signed({9'd0, wsize})); + tidx <= lbase + d_full[12:0]; + det_gt <= (h_reg > th_s); + det_lt <= (h_reg < nth_s); + end + + case (fsm) + S_IDLE: begin + if (start) begin + fsm <= S_LOAD; done <= 1'b0; term <= TERM_NONE; + end else if (cont) begin + done <= 1'b0; term <= TERM_NONE; steps <= 32'd0; + level <= 6'd0; spl_cnt <= 16'd0; sel <= 6'd0; sel_hot <= 64'd1; + have1 <= 1'b0; have2 <= 1'b0; + fsm <= (mode == MODE_T0) ? S_T0 : S_REQ; + end + end + S_LOAD: begin + steps <= 32'd0; level <= 6'd0; spl_cnt <= 16'd0; sel <= 6'd0; sel_hot <= 64'd1; + have1 <= 1'b0; have2 <= 1'b0; prime_cnt <= 4'd0; + fsm <= (mode == MODE_T0) ? S_T0 : S_PRIME; + end + S_PRIME: begin + prime_cnt <= prime_cnt + 4'd1; + if (prime_cnt == PRIME - 1) + fsm <= (bound == 32'd0) ? (final_t0 ? S_FINAL : S_END) : S_REQ; + end + S_T0: begin + if (t0_terminal) begin + term <= (have1 & eq1) ? TERM_FP : ((have2 & eq2) ? TERM_2C : TERM_BOUND); + fsm <= S_END; + end else begin + steps <= steps + 32'd1; + have1 <= 1'b1; + have2 <= have1; + end + end + S_REQ: fsm <= S_IDX; + S_IDX: fsm <= S_TBL; + S_TBL: fsm <= S_COMMIT; + S_COMMIT: begin + if (last_cell) begin + sel <= 6'd0; sel_hot <= 64'd1; + if (mode == MODE_JACOBI) fsm <= S_JAC; + else begin + steps <= steps + 32'd1; + if (last_sweep) fsm <= final_t0 ? S_FINAL : S_END; + else begin + fsm <= S_REQ; + if (spl_cnt + 16'd1 == spl) begin + spl_cnt <= 16'd0; + if (level != levels_m1) level <= level + 6'd1; + end else spl_cnt <= spl_cnt + 16'd1; + end + end + end else begin + sel <= sel + 6'd1; + sel_hot <= {sel_hot[62:0], 1'b0}; + fsm <= S_REQ; + end + end + S_JAC: begin + steps <= steps + 32'd1; + if (last_sweep) fsm <= final_t0 ? S_FINAL : S_END; + else begin + fsm <= S_REQ; + if (spl_cnt + 16'd1 == spl) begin + spl_cnt <= 16'd0; + if (level != levels_m1) level <= level + 6'd1; + end else spl_cnt <= spl_cnt + 16'd1; + end + end + S_FINAL: fsm <= S_END; + S_END: begin + if (rep) fsm <= S_LOAD; + else begin fsm <= S_IDLE; done <= 1'b1; end + end + default: fsm <= S_IDLE; + endcase + + if (stop) begin fsm <= S_IDLE; done <= 1'b0; end + + // ---- register writes ------------------------------------------------------------- + if (reg_stb && reg_we) begin + case (reg_addr) + 16'h0004: begin mode <= reg_wdata[1:0]; rep <= reg_wdata[8]; final_t0 <= reg_wdata[9]; end + 16'h0005: n_m1 <= reg_wdata[5:0]; + 16'h0006: theta <= reg_wdata[19:0]; + 16'h0007: bound <= reg_wdata; + 16'h0008: spl <= reg_wdata[15:0]; + 16'h0009: levels_m1 <= reg_wdata[5:0]; + 16'h0010: x0[31:0] <= reg_wdata; + 16'h0011: x0[63:32] <= reg_wdata; + 16'h0012: win_lo <= reg_wdata[21:0]; + 16'h0013: wbits <= reg_wdata[3:0]; + 16'h0014: if (fsm == S_IDLE) sel <= reg_wdata[5:0]; + 16'h0016: tbl_addr <= reg_wdata[12:0]; + 16'h0017: wa[31:0] <= reg_wdata; + 16'h0018: wa[53:32] <= reg_wdata[21:0]; + 16'h0019: tbl_b_lo <= reg_wdata; + 16'h001A: tbl_addr <= tbl_addr + 13'd1; + 16'h0100: boundary[31:0] <= reg_wdata; + 16'h0101: boundary[63:32] <= reg_wdata; + 16'h0102: boundary[95:64] <= reg_wdata; + 16'h0103: boundary[127:96] <= reg_wdata; + default: ; + endcase + end + + // ---- register reads -------------------------------------------------------------- + if (reg_stb && !reg_we) begin + case (reg_addr) + 16'h0000: reg_rdata <= 32'h4C414E44; // "LAND" + 16'h0001: reg_rdata <= 32'h0040_4002; // 64 cells, 64 levels, rtl version 2 + 16'h0002: reg_rdata <= {28'd0, term, done, busy_o}; + 16'h0004: reg_rdata <= {22'd0, final_t0, rep, 6'd0, mode}; + 16'h0005: reg_rdata <= {26'd0, n_m1}; + 16'h0006: reg_rdata <= {12'd0, theta}; + 16'h0007: reg_rdata <= bound; + 16'h0012: reg_rdata <= {{10{win_lo[21]}}, win_lo}; + 16'h0013: reg_rdata <= {28'd0, wbits}; + 16'h0015: reg_rdata <= {{10{h_reg[21]}}, h_reg}; + 16'h0016: reg_rdata <= {19'd0, tbl_addr}; + 16'h0017: reg_rdata <= tbl_q[31:0]; + 16'h0018: reg_rdata <= {10'd0, tbl_q[53:32]}; + 16'h0019: reg_rdata <= tbl_q[85:54]; + 16'h001A: reg_rdata <= {10'd0, tbl_q[107:86]}; + 16'h001B: begin reg_rdata <= u[31:0]; rd_hi <= u[52:32]; end + 16'h001C: reg_rdata <= {11'd0, rd_hi}; + 16'h0020: begin reg_rdata <= uptime[31:0]; rd_hi <= {5'd0, uptime[47:32]}; end + 16'h0022: begin reg_rdata <= cycles[31:0]; rd_hi <= {5'd0, cycles[47:32]}; end + 16'h0024: begin reg_rdata <= updates[31:0]; rd_hi <= {5'd0, updates[47:32]}; end + 16'h0026: begin reg_rdata <= jobs[31:0]; rd_hi <= {5'd0, jobs[47:32]}; end + 16'h0021, 16'h0023, 16'h0025, 16'h0027: reg_rdata <= {11'd0, rd_hi}; + 16'h0028: reg_rdata <= steps; + 16'h0110: reg_rdata <= state[31:0]; + 16'h0111: reg_rdata <= state[63:32]; + 16'h0112: reg_rdata <= state[95:64]; + 16'h0113: reg_rdata <= state[127:96]; + default: reg_rdata <= 32'hDEAD_BEEF; + endcase + end + end +endmodule + +`default_nettype wire diff --git a/wai/wai-quantum-fpga/rtl/landscape_multi.v b/wai/wai-quantum-fpga/rtl/landscape_multi.v new file mode 100644 index 000000000..299df8577 --- /dev/null +++ b/wai/wai-quantum-fpga/rtl/landscape_multi.v @@ -0,0 +1,51 @@ +// K copies of the engine behind one EMIO port: address bits [15:13] pick the core, [12:0] are the +// core's own map. busy is ORed and done ANDed across cores. The host addresses one core at a time +// (CorePort) or all at once (BroadcastPort). +`timescale 1ns/1ps +`default_nettype none +module landscape_multi #(parameter integer K = 4) ( + input wire clk, + input wire [94:0] emio_o, + output wire [94:0] emio_i +); + (* ASYNC_REG = "TRUE" *) reg req_s1 = 1'b0, req_s2 = 1'b0; + reg req_d = 1'b0; + reg [6:0] por = 7'd0; + wire por_done = por[6]; + always @(posedge clk) begin + req_s1 <= emio_o[80]; + req_s2 <= req_s1; + req_d <= req_s2; + if (!por_done) por <= por + 7'd1; + end + reg stb = 1'b0, we = 1'b0, ack = 1'b0; + reg [15:0] addr = 16'd0; + reg [31:0] wdata = 32'd0; + reg [3:0] wait_cnt = 4'd0; + always @(posedge clk) begin + stb <= 1'b0; + if (por_done && (req_s2 ^ req_d)) begin + addr <= emio_o[79:64]; we <= emio_o[81]; wdata <= emio_o[31:0]; + stb <= 1'b1; wait_cnt <= 4'd4; + end else if (wait_cnt != 4'd0) begin + wait_cnt <= wait_cnt - 4'd1; + if (wait_cnt == 4'd1) ack <= ~ack; + end + end + wire [31:0] rd [0:K-1]; + wire [K-1:0] busy, done; + genvar c; + generate + for (c = 0; c < K; c = c + 1) begin : CORE + landscape_core core ( + .clk(clk), .reg_stb(stb & (addr[15:13] == c)), .reg_we(we), + .reg_addr({3'b000, addr[12:0]}), .reg_wdata(wdata), + .reg_rdata(rd[c]), .busy_o(busy[c]), .done_o(done[c]) + ); + end + endgenerate + reg [31:0] rdata = 32'd0; + always @(posedge clk) rdata <= rd[addr[15:13] % K]; + assign emio_i = {10'd0, &done, |busy, ack, 18'd0, rdata, 32'd0}; +endmodule +`default_nettype wire diff --git a/wai/wai-quantum-fpga/rtl/landscape_port.v b/wai/wai-quantum-fpga/rtl/landscape_port.v new file mode 100644 index 000000000..8bd76b087 --- /dev/null +++ b/wai/wai-quantum-fpga/rtl/landscape_port.v @@ -0,0 +1,66 @@ +// The job port: register reads and writes carried over the PS's EMIO GPIO lines. +// +// Why not AXI. A PS load or store to an AXI address whose PL slave is gone never completes, and it +// takes the whole board with it until someone power-cycles it. This board is shared and remote, and +// another session's reload can replace the design between two accesses. An EMIO line is a PS +// register: with the design gone a read returns junk, the ID check fails, and the job gets an +// error. The cost is speed: about 1.6 us a register access, measured. +// +// The PS offers 95 EMIO lines (banks 3, 4 and 31 bits of 5): +// emio_o[31:0] wdata (bank 3) emio_i[63:32] rdata (bank 4) +// emio_o[79:64] addr (bank 5, 15:0) emio_o[80] req (toggle) emio_o[81] we +// emio_i[82] ack (toggle) emio_i[83] busy emio_i[84] done +// busy and done are wired straight through, so polling for the end of a job takes no transaction. +// +// Handshake: the host writes wdata, then addr/we/req in one register write. The req edge crosses a +// two-flop synchroniser, so by the time it is seen addr, we and wdata have long been stable; they are +// latched then, and ack toggles once the access is done. +`timescale 1ns/1ps +`default_nettype none + +module landscape_port ( + input wire clk, + input wire [94:0] emio_o, + output wire [94:0] emio_i +); + (* ASYNC_REG = "TRUE" *) reg req_s1 = 1'b0, req_s2 = 1'b0; + reg req_d = 1'b0; + reg [6:0] por = 7'd0; // the PS's lines may hold anything when we are configured + wire por_done = por[6]; + always @(posedge clk) begin + req_s1 <= emio_o[80]; + req_s2 <= req_s1; + req_d <= req_s2; + if (!por_done) por <= por + 7'd1; + end + + reg stb = 1'b0, we = 1'b0, ack = 1'b0; + reg [15:0] addr = 16'd0; + reg [31:0] wdata = 32'd0; + reg [3:0] wait_cnt = 4'd0; + wire [31:0] rdata; + wire busy, done; + + always @(posedge clk) begin + stb <= 1'b0; + if (por_done && (req_s2 ^ req_d)) begin + addr <= emio_o[79:64]; + we <= emio_o[81]; + wdata <= emio_o[31:0]; + stb <= 1'b1; + wait_cnt <= 4'd4; + end else if (wait_cnt != 4'd0) begin + wait_cnt <= wait_cnt - 4'd1; + if (wait_cnt == 4'd1) ack <= ~ack; + end + end + + landscape_core core ( + .clk(clk), .reg_stb(stb), .reg_we(we), .reg_addr(addr), .reg_wdata(wdata), + .reg_rdata(rdata), .busy_o(busy), .done_o(done) + ); + + assign emio_i = {10'd0, done, busy, ack, 18'd0, rdata, 32'd0}; +endmodule + +`default_nettype wire diff --git a/wai/wai-quantum-fpga/rtl/landscape_top.v b/wai/wai-quantum-fpga/rtl/landscape_top.v new file mode 100644 index 000000000..7fcfa5a6b --- /dev/null +++ b/wai/wai-quantum-fpga/rtl/landscape_top.v @@ -0,0 +1,9 @@ +// KV260 top: the PS provides pl_clk0 and the EMIO lines; there is no other PL I/O. +`timescale 1ns/1ps +module design_top(); + wire pl_clk0; + wire [94:0] emio_o; + wire [94:0] emio_i; + sys_wrapper ps_i (.pl_clk0(pl_clk0), .emio_o(emio_o), .emio_i(emio_i)); + landscape_port u (.clk(pl_clk0), .emio_o(emio_o), .emio_i(emio_i)); +endmodule diff --git a/wai/wai-quantum-fpga/rtl/landscape_top_multi.v b/wai/wai-quantum-fpga/rtl/landscape_top_multi.v new file mode 100644 index 000000000..8488a2a95 --- /dev/null +++ b/wai/wai-quantum-fpga/rtl/landscape_top_multi.v @@ -0,0 +1,8 @@ +`timescale 1ns/1ps +module design_top(); + wire pl_clk0; + wire [94:0] emio_o; + wire [94:0] emio_i; + sys_wrapper ps_i (.pl_clk0(pl_clk0), .emio_o(emio_o), .emio_i(emio_i)); + landscape_multi #(.K(`LANDSCAPE_K)) u (.clk(pl_clk0), .emio_o(emio_o), .emio_i(emio_i)); +endmodule diff --git a/wai/wai-quantum-fpga/scripts/power_campaign.sh b/wai/wai-quantum-fpga/scripts/power_campaign.sh new file mode 100755 index 000000000..da850222b --- /dev/null +++ b/wai/wai-quantum-fpga/scripts/power_campaign.sh @@ -0,0 +1,42 @@ +#!/bin/sh +# The power campaign, in a fixed order: the run/halt step response, a known-answer vehicle where one +# is present, a null, each job class, then the clock-scaling check. Run ON the board as root, +# detached, and touch nothing on the board until DONE appears (the meter reads the whole board, the +# processor included): +# sudo setsid nohup sh power_campaign.sh [OUTDIR] > /dev/null 2>&1 & +# Needs wai-quantum-fpga and the loaded engine. A known-answer vehicle of your own goes in +# KNOWN_ANSWER (a script taking the bitstream name, settle, passes and samples, printing +# "pass run|halt sample microwatts" lines) and KNOWN_BITSTREAM; without them that step is skipped, +# and the null and the clock-scaling check are the instrument's checks. +cd "$(dirname "$0")" +OUT=${1:-power-$(date -u +%Y%m%dT%H%M%SZ)}/power; mkdir -p "$OUT" +BIT=${ENGINE_BITSTREAM:-landscape.bit.bin} +log() { echo "$(date -u +%FT%TZ) $*" >> "$OUT/campaign.log"; } +foreign() { pgrep -af "${WAI_QUANTUM_FPGA_FOREIGN:-kv_|flashv|flash8|ofpga|fpgautil|aba_|wca_}" | grep -v -e pgrep -e power_campaign -e run_all -e "${KNOWN_ANSWER:-^$}" || true; } +guard() { + f=$(foreign) + if [ -n "$f" ]; then log "STOP: another session is on the board: $f"; echo STOPPED > "$OUT/DONE"; exit 3; fi +} +log "start; $(./wai-quantum-fpga status 2>&1 | tr '\n' ' ')" +guard +./wai-quantum-fpga meter t0-recall 1 0 300 > "$OUT/step.dat" 2>&1; log "step done" +if [ -n "${KNOWN_ANSWER:-}" ] && [ -n "${KNOWN_BITSTREAM:-}" ]; then + guard + sh "$KNOWN_ANSWER" "$KNOWN_BITSTREAM" 60 8 30 > "$OUT/known_answer.dat" 2>&1; log "known answer done exit=$?" + [ -d /sys/class/gpio/gpio594 ] && echo 594 > /sys/class/gpio/unexport + ./wai-quantum-fpga load "$BIT" >> "$OUT/campaign.log" 2>&1 || { log "reload of the engine failed"; echo FAILED > "$OUT/DONE"; exit 2; } +else + log "known answer skipped: KNOWN_ANSWER and KNOWN_BITSTREAM not set" +fi +for spec in "null:t0-recall 8 60 30 --null --predict 0" "t0-recall:t0-recall 10 60 30 --predict 160" \ + "gibbs-recall:gibbs-recall 10 60 30 --predict 12" "t0-idle:t0-idle 8 60 30 --predict 4"; do + name=${spec%%:*}; args=${spec#*:} + guard + ./wai-quantum-fpga meter $args > "$OUT/$name.dat" 2>&1; log "$name done exit=$?" +done +guard +./wai-quantum-fpga clock 30 >> "$OUT/campaign.log" 2>&1 +./wai-quantum-fpga meter t0-recall 6 60 30 --predict 80 > "$OUT/t0-recall-50MHz.dat" 2>&1; log "50 MHz done exit=$?" +./wai-quantum-fpga clock 15 >> "$OUT/campaign.log" 2>&1 +log "end" +echo DONE > "$OUT/DONE" diff --git a/wai/wai-quantum-fpga/scripts/power_tables.py b/wai/wai-quantum-fpga/scripts/power_tables.py new file mode 100644 index 000000000..ac34a5c42 --- /dev/null +++ b/wai/wai-quantum-fpga/scripts/power_tables.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +"""Per-class table from meter logs, by the pre-registered statistic (mean of per-pass differences +of medians, SE across passes), plus a sensitivity column that was NOT pre-registered: the same +with a 10%-trimmed mean inside each point, which keeps sub-quantum information a median of +10 mW-quantised samples throws away. Passes flagged in the log are dropped. + python3 -I power_tables.py a.dat b.dat ... +""" +import statistics as st, sys, re + +def trimmed(xs, f=0.1): + s = sorted(xs); k = int(len(s) * f) + s = s[k:len(s) - k] if k else s + return sum(s) / len(s) + +for path in sys.argv[1:]: + pts, flagged = {}, set() + for line in open(path): + m = re.search(r'pass=(\d+)', line) + if line.startswith('#'): + if m and ('FLAGGED' in line or 'FOREIGN-LOAD' in line or 'NOT-OURS' in line): + flagged.add(int(m.group(1))) + continue + f = line.split() + if len(f) >= 4 and f[0].isdigit() and f[3].isdigit(): + pts.setdefault((int(f[0]), f[1]), []).append(int(f[3]) / 1000.0) + passes = sorted({p for p, _ in pts} - flagged) + dm = [st.median(pts[(p, 'run')]) - st.median(pts[(p, 'halt')]) for p in passes] + dt = [trimmed(pts[(p, 'run')]) - trimmed(pts[(p, 'halt')]) for p in passes] + n = len(passes) + fmt = lambda d: f"{st.mean(d):7.1f} +/- {st.stdev(d) / n ** 0.5:4.1f}" + pr = st.mean(st.median(pts[(p, 'run')]) for p in passes) + print(f"{path.split('/')[-1]:24s} passes {n:2d} medians {fmt(dm)} mW trimmed {fmt(dt)} mW P_run {pr:6.0f} mW flagged {sorted(flagged)}") diff --git a/wai/wai-quantum-fpga/scripts/run_all.sh b/wai/wai-quantum-fpga/scripts/run_all.sh new file mode 100755 index 000000000..f8e42a31a --- /dev/null +++ b/wai/wai-quantum-fpga/scripts/run_all.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# Everything a builder runs on their own board, in order: load the engine, the conformance set, the +# worked examples against the CPU reference, throughput, then the power campaign. On the board, as +# root, from a directory holding wai-quantum-fpga, landscape.bit.bin, this script and +# power_campaign.sh: +# sudo setsid nohup sh run_all.sh > /dev/null 2>&1 & +# and leave the board alone until results-*/power/DONE appears (about three hours). It refuses to +# start, and stops between steps, if another session is flashing or measuring. +cd "$(dirname "$0")" +OUT=results-$(date -u +%Y%m%dT%H%M%SZ) +mkdir -p "$OUT" +FOREIGN=$(pgrep -af "${WAI_QUANTUM_FPGA_FOREIGN:-kv_|flashv|flash8|ofpga|fpgautil|aba_|wca_}" | grep -v -e pgrep -e run_all -e power_campaign || true) +[ -z "$FOREIGN" ] || { echo "another session is on the board: $FOREIGN" > "$OUT/STOPPED"; exit 3; } +{ + echo "fpga manager flags: $(cat /sys/class/fpga_manager/fpga0/flags)" + echo "last FPGA write: $(dmesg | grep 'writing.*FPGA' | tail -1)" +} > "$OUT/board_before.txt" +# A non-zero flags word makes the manager treat a plain bitstream as something else; it is put back. +FLAGS=$(cat /sys/class/fpga_manager/fpga0/flags) +echo 0 > /sys/class/fpga_manager/fpga0/flags +./wai-quantum-fpga load "${ENGINE_BITSTREAM:-landscape.bit.bin}" > "$OUT/load.txt" 2>&1 || { echo "$FLAGS" > /sys/class/fpga_manager/fpga0/flags; exit 2; } +./wai-quantum-fpga status > "$OUT/status.txt" 2>&1 +./wai-quantum-fpga conform > "$OUT/conform.txt" 2>&1 +./wai-quantum-fpga agree --seeds 200 > "$OUT/agree.txt" 2>&1 +./wai-quantum-fpga throughput > "$OUT/throughput.txt" 2>&1 +sh power_campaign.sh "$OUT" +./wai-quantum-fpga release >> "$OUT/status.txt" 2>&1 +echo "$FLAGS" > /sys/class/fpga_manager/fpga0/flags diff --git a/wai/wai-quantum-fpga/scripts/step_settle.py b/wai/wai-quantum-fpga/scripts/step_settle.py new file mode 100755 index 000000000..adafcfac3 --- /dev/null +++ b/wai/wai-quantum-fpga/scripts/step_settle.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +"""Settle time of the run/halt switch, from `vqpu-edge meter 1 0 300` (no settle, 300 samples). + +LAST deviation, not first touch: the latest sample that sits outside a band around the segment's +final level, the band being max(2 sensor quanta = 20 mW, 3 x the tail's SD). The settle used by the +campaign must exceed it, or every point reads the previous state. + python3 -I step_settle.py step.dat +""" +import statistics, sys + +seg = {} +for line in open(sys.argv[1]): + if line.startswith('#') or not line.strip(): + continue + f = line.split() + if len(f) < 4: + continue + seg.setdefault((int(f[0]), f[1]), []).append(int(f[3]) / 1000.0) +for (p, state), xs in sorted(seg.items()): + tail = xs[-60:] + final = statistics.median(tail) + band = max(20.0, 3 * statistics.pstdev(tail)) + last = max((i for i, x in enumerate(xs) if abs(x - final) > band), default=-1) + print(f"pass {p} {state:4s}: final {final:.1f} mW, band +-{band:.1f}, last deviation at {last + 1} s " + f"of {len(xs)}; first 10 s mean {statistics.mean(xs[:10]):.1f} mW") diff --git a/wai/wai-quantum-fpga/src/board.rs b/wai/wai-quantum-fpga/src/board.rs new file mode 100644 index 000000000..262b39a7f --- /dev/null +++ b/wai/wai-quantum-fpga/src/board.rs @@ -0,0 +1,120 @@ +//! Board housekeeping: loading the bitstream and proving the load took, the PL clock, the power +//! sensor, the single-tenant lock, and the checks that another session has not touched the PL. + +use crate::emio::Mem; +use crate::port::{Error, err}; +use std::fs; +use std::process::Command; + +pub const FPGA: &str = "/sys/class/fpga_manager/fpga0"; +const CRL_BASE: u64 = 0xFF5E_0000; +const PL0_REF_CTRL: usize = 0xC0; + +pub fn dmesg_fpga_writes() -> Result, Error> { + let out = Command::new("dmesg").output().map_err(|e| Error(format!("dmesg: {e}")))?; + Ok(String::from_utf8_lossy(&out.stdout) + .lines() + .filter(|l| l.contains("writing") && l.contains("FPGA")) + .map(|l| l.to_string()) + .collect()) +} + +/// Processes that, by the board's written rules, mean another session is flashing or measuring. +pub fn foreign_sessions() -> String { + // Override the patterns for another board's habits: WAI_QUANTUM_FPGA_FOREIGN, an extended regex. + let pat = std::env::var("WAI_QUANTUM_FPGA_FOREIGN").unwrap_or_else(|_| "kv_|flashv|flash8|flashfull|ofpga|fpgautil|aba_|wca_".into()); + let out = Command::new("sh") + .arg("-c") + .arg(format!("pgrep -af '{pat}' | grep -v -e pgrep -e wai-quantum-fpga || true")) + .output(); + out.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()).unwrap_or_default() +} + +/// Loads a bitstream through the FPGA manager and proves the load by its dmesg line. +pub fn load_bitstream(path: &str) -> Result { + let f = foreign_sessions(); + if !f.is_empty() { + return err(format!("another session is active on the board, not loading:\n{f}")); + } + let flags = fs::read_to_string(format!("{FPGA}/flags")).unwrap_or_default(); + if flags.trim() != "0" { + return err(format!("fpga manager flags = {} (want 0)", flags.trim())); + } + let name = std::path::Path::new(path).file_name().and_then(|s| s.to_str()).ok_or_else(|| Error("bad path".into()))?.to_string(); + let fw = format!("/lib/firmware/{name}"); + if std::path::Path::new(path) != std::path::Path::new(&fw) { + fs::copy(path, &fw).map_err(|e| Error(format!("copy to {fw}: {e}")))?; + } + let before = dmesg_fpga_writes()?.iter().filter(|l| l.contains(&format!("writing {name} "))).count(); + fs::write(format!("{FPGA}/firmware"), &name).map_err(|e| Error(format!("fpga manager: {e}")))?; + std::thread::sleep(std::time::Duration::from_secs(3)); + let w = dmesg_fpga_writes()?; + let after = w.iter().filter(|l| l.contains(&format!("writing {name} "))).count(); + if after <= before { + return err(format!("LOAD-NOOP: no new dmesg line for {name}; the old design is still running")); + } + let last = w.last().cloned().unwrap_or_default(); + if last.contains("fail") || last.contains("error") { + return err(format!("load reported an error: {last}")); + } + Ok(last) +} + +/// pl_clk0 = IOPLL / div0 / div1. Returns (raw register, MHz) assuming the 1500 MHz source this +/// board's IOPLL runs at; the engine's uptime counter checks it by behaviour. +pub fn pl0_ctrl() -> Result<(u32, f64), Error> { + let m = Mem::map(CRL_BASE, 0x1000)?; + let v = m.rd(PL0_REF_CTRL); + let d0 = ((v >> 8) & 0x3F).max(1); + let d1 = ((v >> 16) & 0x3F).max(1); + Ok((v, 1500.0 / (d0 * d1) as f64)) +} + +/// Sets div0 for pl_clk0 (div1 = 1). 15 -> 100 MHz. Returns the previous register value. +pub fn set_pl0_div(div0: u32) -> Result { + if !(4..=63).contains(&div0) { + return err("div0 must be 4..63"); + } + let m = Mem::map(CRL_BASE, 0x1000)?; + let old = m.rd(PL0_REF_CTRL); + let new = (old & !(0x3F << 8) & !(0x3F << 16)) | (div0 << 8) | (1 << 16) | (1 << 24); + m.wr(PL0_REF_CTRL, new); + Ok(old) +} + +pub fn restore_pl0(v: u32) -> Result<(), Error> { + let m = Mem::map(CRL_BASE, 0x1000)?; + m.wr(PL0_REF_CTRL, v); + Ok(()) +} + +pub fn find_power_sensor() -> Result { + for e in fs::read_dir("/sys/class/hwmon").map_err(|e| Error(e.to_string()))?.flatten() { + let p = e.path(); + if fs::read_to_string(p.join("name")).map(|s| s.trim() == "ina260_u14").unwrap_or(false) { + return Ok(p.join("power1_input").to_string_lossy().to_string()); + } + } + err("no ina260_u14 power sensor") +} + +pub fn read_uw(path: &str) -> Result { + fs::read_to_string(path).map_err(|e| Error(e.to_string()))?.trim().parse::().map_err(|e| Error(e.to_string())) +} + +unsafe extern "C" { + fn flock(fd: i32, op: i32) -> i32; +} + +/// The board is single-tenant: jobs queue on this lock, in the order the kernel grants it. +pub struct Lock(#[allow(dead_code)] fs::File); +pub fn lock() -> Result { + use std::os::unix::io::AsRawFd; + let f = fs::OpenOptions::new().create(true).write(true).truncate(false).open("/run/lock/wai-quantum-fpga.lock") + .or_else(|_| fs::OpenOptions::new().create(true).write(true).truncate(false).open("/tmp/wai-quantum-fpga.lock")) + .map_err(|e| Error(format!("lock file: {e}")))?; + if unsafe { flock(f.as_raw_fd(), 2) } != 0 { + return err("flock failed"); + } + Ok(Lock(f)) +} diff --git a/wai/wai-quantum-fpga/src/conform.rs b/wai/wai-quantum-fpga/src/conform.rs new file mode 100644 index 000000000..d9cb5e8ae --- /dev/null +++ b/wai/wai-quantum-fpga/src/conform.rs @@ -0,0 +1,361 @@ +//! The checks, written once against any port: the board's, the model's, or the testbench recording. +//! +//! - [`bits`]: the conformance set's `settle` and `chains` vectors, which a bit-exact kernel gives exactly. +//! - [`full`]: those, then `cells` and `coupled` by frequency (and the cells' draws against the +//! reference's, draw for draw), the all-at-once control the coupled test must catch, random +//! settles up to 64 cells, field read-back, and `jobs`: every worked example at seed 1. +//! - [`agree`]: every worked example over many seeds, at its own temperature and at 0, on the board +//! and on the CPU reference, answer for answer. + +use crate::engine::Loaded; +use crate::member::{BoardMember, VECTORS, lowered, plan}; +use crate::port::Port; +use crate::regs::*; +use serde_json::{Value, json}; +use std::fmt::Write as _; +use wai_quantum::quantum_landscape::EXAMPLES; +use wai_quantum::quantum_landscape::energy::Landscape; +use wai_quantum::quantum_landscape::job; +use wai_quantum::quantum_landscape::member::{Chain, CpuReference, Member}; +use wai_quantum::quantum_landscape::program::read; +use wai_quantum::quantum_landscape::run::{Exact, run_on, state_text}; +use wai_quantum::quantum_landscape::sample::{cell, cuts, out, pick}; +use wai_quantum::quantum_landscape::settle::{Workload, state_hash}; + +#[derive(Default)] +pub struct Report { + pub text: String, + pub failures: Vec, + pub checks: usize, +} + +impl Report { + pub fn line(&mut self, s: impl AsRef) { + self.text.push_str(s.as_ref()); + self.text.push('\n'); + } + pub fn check(&mut self, ok: bool, what: impl Into) { + let w = what.into(); + self.checks += 1; + let _ = writeln!(self.text, " [{}] {}", if ok { "PASS" } else { "FAIL" }, w); + if !ok { + self.failures.push(w); + } + } +} + +pub struct Bits { + pub settle_ok: bool, + pub chains_ok: bool, + pub text: String, +} + +fn vectors() -> Value { + serde_json::from_str(VECTORS).expect("the conformance set is JSON") +} + +fn cells_of(text: &str) -> Vec { + text.chars().map(|c| match c { '+' => 1, '-' => -1, _ => 0 }).collect() +} + +fn landscape(v: &Value) -> Landscape { + let mut l = Landscape::new(v["n"].as_u64().unwrap() as usize); + for c in v["couplings"].as_array().unwrap() { + l.couple(c[0].as_u64().unwrap() as usize, c[1].as_u64().unwrap() as usize, c[2].as_i64().unwrap() as i8); + } + for (i, b) in v["field"].as_array().unwrap().iter().enumerate() { + l.set_field(i, b.as_i64().unwrap()); + } + l.set_cost(v["cost"].as_i64().unwrap()); + l +} + +/// The `settle` and `chains` vectors on the member: what [`BoardMember::earn`] grades. +pub fn bits(m: &BoardMember

) -> Result { + let f = vectors(); + let mut text = String::new(); + let (mut s_ok, mut s_n) = (0, 0); + for c in f["settle"].as_array().unwrap() { + s_n += 1; + let w = landscape(c).lower(); + let o = m.settle(&w, &cells_of(c["start"].as_str().unwrap()), c["bound"].as_u64().unwrap() as u32)?; + let e = &c["expect"]; + let got = (state_text(&o.state), u64::from(o.sweeps), o.term.label().to_string(), o.accumulates, format!("{:016x}", state_hash(&o.state))); + let want = (e["state"].as_str().unwrap().to_string(), e["sweeps"].as_u64().unwrap(), e["term"].as_str().unwrap().to_string(), e["accumulates"].as_u64().unwrap(), e["state_hash"].as_str().unwrap().to_string()); + if got == want { + s_ok += 1; + } else { + let _ = writeln!(text, " settle {}: got {:?}, want {:?}", c["name"], got, want); + } + } + let (mut c_ok, mut c_n) = (0, 0); + for c in f["chains"].as_array().unwrap() { + c_n += 1; + let w = landscape(c).lower(); + let chain = Chain { start: cells_of(c["start"].as_str().unwrap()), seed: c["chain"].as_str().unwrap().parse().unwrap() }; + let o = m.chains(&w, &[chain], c["t0"].as_f64().unwrap(), c["sweeps"].as_u64().unwrap() as u32)?.remove(0); + let e = &c["expect"]; + let got = (state_text(&o.state), o.accumulates, o.draws, format!("{:016x}", state_hash(&o.state))); + let want = (e["state"].as_str().unwrap().to_string(), e["accumulates"].as_u64().unwrap(), e["draws"].as_u64().unwrap(), e["state_hash"].as_str().unwrap().to_string()); + if got == want { + c_ok += 1; + } else { + let _ = writeln!(text, " chain {}: got {:?}, want {:?}", c["name"], got, want); + } + } + let _ = writeln!(text, "settle vectors {s_ok}/{s_n} bit for bit; chain vectors {c_ok}/{c_n} bit for bit"); + Ok(Bits { settle_ok: s_ok == s_n, chains_ok: c_ok == c_n, text }) +} + +/// Draws one cell, or two coupled cells, sweep after sweep at one temperature, by CONT on the +/// engine, so every draw is the board's. +fn chain_states(m: &BoardMember

, l: &Loaded, t: f64, mode: u32, seed: u64, sweeps: u64) -> Result>, String> { + let seg = plan(l, &[t])?.remove(0); + let mut e = m.engine.borrow_mut(); + e.load(l).map_err(|e| e.0)?; + e.load_table(seg.win_lo, seg.wbits, seg.first, &seg.levels).map_err(|e| e.0)?; + e.chain(seed).map_err(|e| e.0)?; + let mut out = Vec::with_capacity(sweeps as usize); + out.push(e.run_sweeps(mode, &vec![0; l.n], 1, 1, false).map_err(|e| e.0)?.state); + for _ in 1..sweeps { + out.push(e.cont_sweeps(mode, 1, 1, false).map_err(|e| e.0)?.state); + } + Ok(out) +} + +/// Every check, at `scale` times the conformance set's draw counts. +pub fn full(m: &BoardMember

, scale: f64) -> Result { + let f = vectors(); + let mut rep = Report::default(); + let b = bits(m)?; + rep.line("wai.quantum.landscape/1 member tests (quantum-conformance/landscape/vectors.json)"); + rep.text.push_str(&b.text); + rep.check(b.settle_ok, "settle kernel: the settle vectors, bit for bit (state, sweeps, ending, accumulates, state hash)"); + rep.check(b.chains_ok, "sample kernel: the chain vectors, bit for bit (state, accumulates, draws, state hash)"); + + let st = &f["statistical"]; + let draws = ((st["draws"].as_u64().unwrap() as f64) * scale).max(2000.0) as u64; + let ses = st["standard_errors"].as_f64().unwrap(); + for c in f["cells"].as_array().unwrap() { + let (h, cost, t) = (c["field"].as_i64().unwrap(), c["cost"].as_i64().unwrap(), c["temperature"].as_f64().unwrap()); + let wts: Vec = c["weights"].as_array().unwrap().iter().map(|x| x.as_f64().unwrap()).collect(); + let l = Loaded { n: 1, rows: vec![vec![]], fields: vec![h], cost }; + let seed = 0x5EED ^ (h as u64).wrapping_mul(31) ^ (cost as u64).wrapping_mul(97); + let states = chain_states(m, &l, t, MODE_GIBBS, seed, draws)?; + let mut cnt = [0u64; 3]; + let mut same = 0u64; + for (j, s) in states.iter().enumerate() { + cnt[(s[0] + 1) as usize] += 1; + if s[0] == cell(h, cost, 1.0 / t, seed, j as u64 + 1) { + same += 1; + } + } + let n = draws as f64; + let z: Vec = (0..3) + .map(|i| { + let sd = (n * wts[i] * (1.0 - wts[i])).sqrt(); + let d = cnt[i] as f64 - n * wts[i]; + if sd > 0.0 { d / sd } else if d.abs() < 1.0 { 0.0 } else { f64::INFINITY } + }) + .collect(); + rep.check( + z.iter().all(|x| x.abs() <= ses) && same == draws, + format!("cell h={h} c={cost} T={t}: {draws} draws, counts {cnt:?}, z {:+.2} {:+.2} {:+.2} (limit {ses}); {same}/{draws} equal to the reference's draw", z[0], z[1], z[2]), + ); + } + + // the coupled test always runs its full count: its limit is set for that many sweeps + let csweeps = st["coupled_sweeps"].as_u64().unwrap(); + let within = st["coupled_within"].as_f64().unwrap(); + for c in f["coupled"].as_array().unwrap() { + let (wc, cost, t) = (c["coupling"].as_i64().unwrap() as i8, c["cost"].as_i64().unwrap(), c["temperature"].as_f64().unwrap()); + let wts: Vec = c["weights"].as_array().unwrap().iter().map(|x| x.as_f64().unwrap()).collect(); + let l = Loaded { n: 2, rows: vec![vec![(1, wc)], vec![(0, wc)]], fields: vec![0, 0], cost }; + let seed = 0xC0DE ^ (cost as u64); + for (mode, label) in [(MODE_GIBBS, "sequential"), (MODE_JACOBI, "all-at-once CONTROL")] { + let states = chain_states(m, &l, t, mode, seed, csweeps)?; + let mut cnt = [0u64; 9]; + for s in &states { + cnt[(3 * (s[0] + 1) + (s[1] + 1)) as usize] += 1; + } + let worst = (0..9).map(|i| (cnt[i] as f64 / csweeps as f64 - wts[i]).abs()).fold(0.0, f64::max); + if mode == MODE_GIBBS { + // the same chain in software, draw for draw: sequential, the reference's draws and cut points + let beta = 1.0 / t; + let mut s = [0i8; 2]; + let mut same = 0u64; + for (k, got) in states.iter().enumerate() { + for i in 0..2 { + let h = i64::from(wc) * i64::from(s[1 - i]); + let (a, b) = cuts(h, cost, beta); + s[i] = pick(out(seed, (2 * k + i) as u64 + 1) >> 11, a, b); + } + if got[..] == s[..] { + same += 1; + } + } + rep.check(worst <= within && same == csweeps, format!("coupled W={wc} c={cost} T={t}, {label}: worst joint error {worst:.4} over {csweeps} sweeps (limit {within}); {same}/{csweeps} sweeps equal to the reference's")); + } else { + rep.check(worst > 2.0 * within, format!("coupled W={wc} c={cost} T={t}, {label} must miss by more than {}: worst joint error {worst:.4}", 2.0 * within)); + } + } + } + + let (mut ok, mut n) = (0, 0); + let mut first = String::new(); + for j in f["jobs"].as_array().unwrap() { + n += 1; + let req = json!({ "program": j["program"], "seed": j["seed"] }); + let e = &j["expect"]; + let got = match job::call_on(m, "run", &req) { + Ok(a) => json!({ "state": a["state"], "energy": a["energy"], "verdict": a["verdict"], "accumulates": a["work"]["accumulates"], "draws": a["work"]["draws"], "replay": a["replay"] }), + Err(r) => json!({ "refused": r.plain(), "line": r.line }), + }; + if &got == e { + ok += 1; + } else if first.is_empty() { + first = format!(" first difference: {}", j["name"]); + } + } + rep.check(ok == n, format!("jobs: {ok}/{n} worked examples at seed 1 answer as the set says (state, energy, verdict, accumulates, draws, replay digest){first}")); + + rep.text.push_str(&random_settles(m, ((2000.0 * scale) as usize).max(50), 0xA11CE)?.text); + Ok(rep) +} + +/// Random workloads up to 64 cells, with fields and a cost, against the reference's settle. +pub fn random_settles(m: &BoardMember

, count: usize, seed: u64) -> Result { + let mut rep = Report::default(); + let mut z = seed; + let mut next = || { + z = z.wrapping_add(GOLDEN); + let mut r = z; + r = (r ^ (r >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); + r = (r ^ (r >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); + r ^ (r >> 31) + }; + let mut bad = 0; + for _ in 0..count { + let n = 2 + (next() % 63) as usize; + let dens = 5 + next() % 90; + let mut l = Landscape::new(n); + for i in 0..n { + for j in (i + 1)..n { + if next() % 100 < dens { + l.couple(i, j, if next() & 1 == 0 { 1 } else { -1 }); + } + } + l.set_field(i, (next() % 11) as i64 - 5); + } + l.set_cost((next() % 4) as i64); + let start: Vec = (0..n).map(|_| (next() % 3) as i8 - 1).collect(); + let bound = 1 + (next() % 80) as u32; + let w = l.lower(); + if m.settle(&w, &start, bound)? != w.settle(&start, bound) { + bad += 1; + } + } + rep.check(bad == 0, format!("{}/{count} random workloads (2-64 cells, fields -5..5, cost 0-3) settle exactly as the reference does", count - bad)); + Ok(rep) +} + +/// Every field read back from the engine at random states, against W s + b. +pub fn fields(m: &BoardMember

, states: usize) -> Result { + let mut rep = Report::default(); + let mut l = Landscape::new(64); + let mut z = 0xF1E1Du64; + let mut next = || { + z ^= z << 13; + z ^= z >> 7; + z ^= z << 17; + z + }; + for i in 0..64 { + for j in (i + 1)..64 { + let r = next() % 3; + if r != 0 { + l.couple(i, j, if r == 1 { 1 } else { -1 }); + } + } + l.set_field(i, (next() % (2 * FIELD_MAX as u64 + 1)) as i64 - FIELD_MAX); + } + let w: Workload = l.lower(); + let lw = lowered(&w); + let mut e = m.engine.borrow_mut(); + e.load(&lw).map_err(|e| e.0)?; + let mut bad = 0; + for _ in 0..states { + let s: Vec = (0..64).map(|_| (next() % 3) as i8 - 1).collect(); + e.load_state(&s).map_err(|e| e.0)?; + for i in 0..64 { + if e.field(i).map_err(|e| e.0)? != w.field(&s, i) { + bad += 1; + } + } + } + rep.check(bad == 0, format!("{}/{} fields read back equal W s + b (dense W, fields across the engine's ±{FIELD_MAX})", states * 64 - bad, states * 64)); + Ok(rep) +} + +/// Every worked example, `seeds` seeds, at its own temperature and at 0: the board's answer +/// against the CPU reference's, field for field (the member's name and label aside). +pub fn agree(m: &BoardMember

, seeds: u64) -> Result { + let mut rep = Report::default(); + rep.line(format!("the worked examples on the board and on the CPU reference, {seeds} seeds each")); + let key = |a: &Value| json!([a["state"], a["energy"], a["verdict"], a["work"], a["replay"], a["truth"], a["reached"], a["checks"], a["mode"], a["temperature"]]); + for (name, text) in EXAMPLES { + let p = match read(text) { + Ok(p) => p, + Err(r) => { + rep.check(true, format!("{name}: refused by the reader before any member runs: {}", r.plain())); + continue; + } + }; + let exact = Exact::of(&p); + for temp in [None, Some(0.0)] { + let (mut same, mut n) = (0u64, 0u64); + let mut first = String::new(); + for s in 1..=seeds { + n += 1; + let a = run_on(m, &p, &exact, s, temp).map_err(|r| r.plain())?; + let b = run_on(&CpuReference, &p, &exact, s, temp).map_err(|r| r.plain())?; + if key(&a) == key(&b) { + same += 1; + } else if first.is_empty() { + first = format!("; first difference at seed {s}: board {} vs reference {}", a["state"], b["state"]); + } + } + let t = match temp { None => "its own temperature".to_string(), Some(t) => format!("temperature {t}") }; + rep.check(same == n, format!("{name} at {t}: {same}/{n} answers identical{first}")); + } + } + Ok(rep) +} + +/// A short session for the RTL testbench: both kernels through the member, a schedule longer than +/// the table, the all-at-once mode, a field outside the window, table and draw read-back. +pub fn tb_session(m: &BoardMember

) -> Result<(), String> { + bits(m)?; + fields(m, 2)?; + let mut l = Landscape::new(6); + for i in 0..5 { + l.couple(i, i + 1, if i % 2 == 0 { 1 } else { -1 }); + } + l.set_field(0, 3).set_field(5, 500).set_cost(1); + let w = l.lower(); + let start = [1i8, 0, -1, 0, 1, -1]; + m.chains(&w, &[Chain { start: start.to_vec(), seed: 77 }], 3.0, 140)?; + m.chains(&w, &[Chain { start: start.to_vec(), seed: 78 }, Chain { start: vec![0; 6], seed: 79 }], 0.0, 9)?; + let lw = lowered(&w); + let seg = plan(&lw, &[1.5, 0.75])?.remove(0); + let mut e = m.engine.borrow_mut(); + e.load_table(seg.win_lo, seg.wbits, seg.first, &seg.levels).map_err(|e| e.0)?; + e.chain(5).map_err(|e| e.0)?; + e.run_sweeps(MODE_JACOBI, &start, 2, 1, false).map_err(|e| e.0)?; + e.next_draw().map_err(|e| e.0)?; + e.table_entry(((1 << seg.wbits) + (seg.first - seg.win_lo)) as usize).map_err(|e| e.0)?; + for c in [UPTIME, CYCLES, UPDATES, JOBS] { + e.counter(c).map_err(|e| e.0)?; + } + Ok(()) +} diff --git a/wai/wai-quantum-fpga/src/emio.rs b/wai/wai-quantum-fpga/src/emio.rs new file mode 100644 index 000000000..4773fc316 --- /dev/null +++ b/wai/wai-quantum-fpga/src/emio.rs @@ -0,0 +1,164 @@ +//! The board's port: the PS GPIO controller's EMIO banks, reached through /dev/mem. See +//! `rtl/landscape_port.v` for the PL side and why this is not AXI. +//! +//! Bank 3 (EMIO 31:0) wdata out; bank 4 (EMIO 63:32) rdata in; bank 5 (EMIO 94:64): addr 15:0, +//! req 16, we 17 out, ack 18, busy 19, done 20 in. The directions this sets are restored on drop. + +use crate::port::{Error, Port, err}; +use std::fs::OpenOptions; +use std::os::unix::fs::OpenOptionsExt; +use std::os::unix::io::AsRawFd; +use std::time::{Duration, Instant}; + +unsafe extern "C" { + fn mmap(addr: *mut u8, len: usize, prot: i32, flags: i32, fd: i32, off: i64) -> *mut u8; + fn munmap(addr: *mut u8, len: usize) -> i32; +} +const PROT_RW: i32 = 1 | 2; +const MAP_SHARED: i32 = 1; +const O_SYNC: i32 = 0o4010000; + +pub struct Mem { + p: *mut u8, + len: usize, +} +impl Mem { + pub fn map(base: u64, len: usize) -> Result { + let f = OpenOptions::new() + .read(true) + .write(true) + .custom_flags(O_SYNC) + .open("/dev/mem") + .map_err(|e| Error(format!("/dev/mem: {e} (run as root)")))?; + let p = unsafe { mmap(std::ptr::null_mut(), len, PROT_RW, MAP_SHARED, f.as_raw_fd(), base as i64) }; + if p as isize == -1 || p.is_null() { + return err(format!("mmap of {base:#x} failed")); + } + Ok(Mem { p, len }) + } + #[inline] + pub fn rd(&self, off: usize) -> u32 { + unsafe { std::ptr::read_volatile(self.p.add(off) as *const u32) } + } + #[inline] + pub fn wr(&self, off: usize, v: u32) { + unsafe { std::ptr::write_volatile(self.p.add(off) as *mut u32, v) } + } +} +impl Drop for Mem { + fn drop(&mut self) { + unsafe { + munmap(self.p, self.len); + } + } +} + +const GPIO_BASE: u64 = 0xFF0A_0000; +const DATA_3: usize = 0x04C; +const DATA_5: usize = 0x054; +const DATA_4_RO: usize = 0x070; +const DATA_5_RO: usize = 0x074; +const DIRM_3: usize = 0x2C4; +const OEN_3: usize = 0x2C8; +const DIRM_4: usize = 0x304; +const OEN_4: usize = 0x308; +const DIRM_5: usize = 0x344; +const OEN_5: usize = 0x348; +const OUT5: u32 = 0x0003_FFFF; +const ACK: u32 = 1 << 18; +const BUSY: u32 = 1 << 19; + +pub struct EmioPort { + m: Mem, + req: u32, + saved: [u32; 7], + pub txns: u64, +} + +impl EmioPort { + pub fn open() -> Result { + let m = Mem::map(GPIO_BASE, 0x1000)?; + let saved = [m.rd(DIRM_3), m.rd(OEN_3), m.rd(DIRM_4), m.rd(OEN_4), m.rd(DIRM_5), m.rd(OEN_5), m.rd(DATA_5)]; + m.wr(DIRM_3, 0xFFFF_FFFF); + m.wr(OEN_3, 0xFFFF_FFFF); + m.wr(DIRM_4, 0); + m.wr(OEN_4, 0); + m.wr(DIRM_5, OUT5); + m.wr(OEN_5, OUT5); + let req = (m.rd(DATA_5) >> 16) & 1; + Ok(EmioPort { m, req, saved, txns: 0 }) + } + + fn txn(&mut self, we: bool, addr: u16, wdata: u32) -> Result { + let ack0 = self.m.rd(DATA_5_RO) & ACK; + if we { + self.m.wr(DATA_3, wdata); + } + self.req ^= 1; + self.m.wr(DATA_5, ((we as u32) << 17) | (self.req << 16) | addr as u32); + let t = Instant::now(); + let mut spins = 0u32; + while self.m.rd(DATA_5_RO) & ACK == ack0 { + spins += 1; + if spins > 64 && t.elapsed() > Duration::from_millis(50) { + return err(format!( + "no ack from the PL for {} {addr:#06x}: the landscape engine bitstream is not loaded, its clock is stopped, or another session replaced it", + if we { "write" } else { "read" } + )); + } + } + self.txns += 1; + Ok(if we { 0 } else { self.m.rd(DATA_4_RO) }) + } + + /// Puts the three EMIO banks back to inputs with outputs disabled, the state this board boots in. + /// For the end of a session, or after a process that held the port was killed before its drop. + pub fn release_to_boot_state() -> Result<(), Error> { + let m = Mem::map(GPIO_BASE, 0x1000)?; + for (d, o) in [(DIRM_3, OEN_3), (DIRM_4, OEN_4), (DIRM_5, OEN_5)] { + m.wr(o, 0); + m.wr(d, 0); + } + m.wr(DATA_3, 0); + m.wr(DATA_5, 0); + Ok(()) + } + + pub fn busy(&self) -> bool { + self.m.rd(DATA_5_RO) & BUSY != 0 + } +} + +impl Port for EmioPort { + fn write(&mut self, addr: u16, val: u32) -> Result<(), Error> { + self.txn(true, addr, val).map(|_| ()) + } + fn read(&mut self, addr: u16) -> Result { + self.txn(false, addr, 0) + } + fn wait_idle(&mut self, timeout: Duration) -> Result<(), Error> { + let t = Instant::now(); + loop { + if !self.busy() { + return Ok(()); + } + if t.elapsed() > timeout { + return err("the engine stayed busy past the timeout"); + } + std::hint::spin_loop(); + } + } +} + +impl Drop for EmioPort { + fn drop(&mut self) { + let s = self.saved; + self.m.wr(DATA_5, s[6]); + self.m.wr(DIRM_3, s[0]); + self.m.wr(OEN_3, s[1]); + self.m.wr(DIRM_4, s[2]); + self.m.wr(OEN_4, s[3]); + self.m.wr(DIRM_5, s[4]); + self.m.wr(OEN_5, s[5]); + } +} diff --git a/wai/wai-quantum-fpga/src/engine.rs b/wai/wai-quantum-fpga/src/engine.rs new file mode 100644 index 000000000..58920d7b5 --- /dev/null +++ b/wai/wai-quantum-fpga/src/engine.rs @@ -0,0 +1,335 @@ +//! The driver: put a workload, a cut-point table and a draw counter into the engine, run it, read +//! it back. Generic over the port, so the same code drives the board, the model and the testbench. + +use crate::port::{Error, Port, err}; +use crate::regs::*; +use std::time::Duration; + +pub fn encode(s: &[i8]) -> [u32; 4] { + let mut w = [0u32; 4]; + for (j, &t) in s.iter().enumerate() { + let v: u32 = match t { + 1 => 0b01, + -1 => 0b10, + _ => 0, + }; + let b = 2 * j; + w[b >> 5] |= v << (b & 31); + } + w +} + +pub fn decode(w: &[u32; 4], n: usize) -> Result, Error> { + (0..n) + .map(|j| { + let b = 2 * j; + match (w[b >> 5] >> (b & 31)) & 3 { + 0b00 => Ok(0), + 0b01 => Ok(1), + 0b10 => Ok(-1), + _ => err(format!("cell {j} reads back as the illegal pair (1,1)")), + } + }) + .collect() +} + +#[derive(Clone, Debug, PartialEq)] +pub struct T0Run { + pub state: Vec, + pub steps: u32, + pub term: u32, + pub cycles: u64, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SweepRun { + pub state: Vec, + pub sweeps: u32, + pub cycles: u64, + pub updates: u64, +} + +/// A workload as the engine holds it: rows of (column, ±1), one field a cell, the cost. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Loaded { + pub n: usize, + pub rows: Vec>, + pub fields: Vec, + pub cost: i64, +} + +impl Loaded { + pub fn nonzeros(&self) -> u64 { + self.rows.iter().map(|r| r.len() as u64).sum() + } + pub fn key(&self) -> [u8; 32] { + let mut h = blake3::Hasher::new(); + h.update(&(self.n as u64).to_le_bytes()); + for r in &self.rows { + h.update(&(r.len() as u64).to_le_bytes()); + for &(j, v) in r { + h.update(&(j as u64).to_le_bytes()); + h.update(&[v as u8]); + } + } + for f in &self.fields { + h.update(&f.to_le_bytes()); + } + h.update(&self.cost.to_le_bytes()); + *h.finalize().as_bytes() + } +} + +pub struct Engine { + pub port: P, + pub timeout: Duration, + n: usize, + loaded: Option<[u8; 32]>, + table: Option<[u8; 32]>, +} + +impl Engine

{ + /// Opens the engine. Refuses anything whose ID and version are not this design's: on a shared + /// board that is how a foreign bitstream is caught before it is driven. + pub fn open(mut port: P) -> Result { + let id = port.read(ID)?; + if id != ID_VALUE { + return err(format!( + "the programmable logic is not running the landscape engine (ID {id:#010x}, want {ID_VALUE:#010x}). \ + Load the bitstream first; if it was loaded, another session has replaced it." + )); + } + let v = port.read(VERSION)?; + if v != VERSION_VALUE { + return err(format!("engine version {v:#010x}, this host speaks {VERSION_VALUE:#010x}")); + } + Ok(Engine { port, timeout: Duration::from_secs(60), n: 0, loaded: None, table: None }) + } + + pub fn n(&self) -> usize { + self.n + } + + pub fn counter(&mut self, lo: u16) -> Result { + let l = self.port.read(lo)? as u64; + let h = self.port.read(lo + 1)? as u64; + Ok((h << 32) | l) + } + + /// Writes W, the fields and the cost, unless the same workload is already there. + pub fn load(&mut self, w: &Loaded) -> Result<(), Error> { + if w.n == 0 || w.n > CELLS { + return err(format!("{} variables; this engine holds 1 to {CELLS}", w.n)); + } + if let Some(i) = w.fields.iter().position(|f| f.abs() > FIELD_MAX) { + return err(format!("variable {i} has the field {}, beyond the engine's ±{FIELD_MAX}", w.fields[i])); + } + if !(0..=COST_MAX).contains(&w.cost) { + return err(format!("the cost {} is beyond the engine's 0 to {COST_MAX}", w.cost)); + } + let key = w.key(); + if self.loaded == Some(key) { + self.n = w.n; + return Ok(()); + } + self.loaded = None; + self.port.write(CONTROL, CTL_CLEARW)?; + for (i, row) in w.rows.iter().enumerate() { + let mut cells = vec![0i8; CELLS]; + for &(j, v) in row { + if !(v == 1 || v == -1) || j >= w.n { + return err(format!("row {i}: a coupling must be ±1 to a variable below {}", w.n)); + } + cells[j] = v; + } + for (k, &word) in encode(&cells).iter().enumerate() { + if word != 0 { + self.port.write(W + (i as u16) * 4 + k as u16, word)?; + } + } + if w.fields[i] != 0 { + self.port.write(FIELDS + i as u16, (w.fields[i] as u32) & 0xF_FFFF)?; + } + } + self.port.write(N_M1, (w.n - 1) as u32)?; + self.port.write(COST, w.cost as u32)?; + self.n = w.n; + self.loaded = Some(key); + Ok(()) + } + + /// Writes the window and its cut points: `levels[l][k]` is the pair for field `first + k` at + /// level `l`. Skipped when the same table is already there. + pub fn load_table(&mut self, win_lo: i64, wbits: u32, first: i64, levels: &[Vec<(u64, u64)>]) -> Result<(), Error> { + if wbits > MAX_WBITS || levels.is_empty() || levels.len() > (TABLE >> wbits).min(LEVELS) { + return err(format!("{} levels of 2^{wbits} fields do not fit the table", levels.len())); + } + let mut h = blake3::Hasher::new(); + h.update(&win_lo.to_le_bytes()); + h.update(&wbits.to_le_bytes()); + h.update(&first.to_le_bytes()); + for l in levels { + h.update(&(l.len() as u64).to_le_bytes()); + for &(a, b) in l { + h.update(&a.to_le_bytes()); + h.update(&b.to_le_bytes()); + } + } + let key = *h.finalize().as_bytes(); + self.port.write(WIN_LO, (win_lo as u32) & 0x3F_FFFF)?; + self.port.write(WBITS, wbits)?; + self.port.write(LEVELS_M1, (levels.len() - 1) as u32)?; + if self.table == Some(key) { + return Ok(()); + } + self.table = None; + for (l, entries) in levels.iter().enumerate() { + if entries.is_empty() { + continue; + } + let off = first - win_lo; + if off < 0 || off as usize + entries.len() > (1usize << wbits) { + return err("table entries outside the window"); + } + self.port.write(TBL_ADDR, ((l << wbits) + off as usize) as u32)?; + for &(a, b) in entries { + self.port.write(TBL_A_LO, a as u32)?; + self.port.write(TBL_A_HI, (a >> 32) as u32)?; + self.port.write(TBL_B_LO, b as u32)?; + self.port.write(TBL_B_HI, (b >> 32) as u32)?; + } + } + self.table = Some(key); + Ok(()) + } + + /// Reads back one entry of the table. + pub fn table_entry(&mut self, addr: usize) -> Result<(u64, u64), Error> { + self.port.write(TBL_ADDR, addr as u32)?; + let a = self.port.read(TBL_A_LO)? as u64 | ((self.port.read(TBL_A_HI)? as u64) << 32); + let b = self.port.read(TBL_B_LO)? as u64 | ((self.port.read(TBL_B_HI)? as u64) << 32); + Ok((a, b)) + } + + /// The draw counter's start for a chain: its seed plus GOLDEN, so the first draw is out(chain, 1). + pub fn chain(&mut self, chain_seed: u64) -> Result<(), Error> { + let x0 = chain_seed.wrapping_add(GOLDEN); + self.port.write(X0_LO, x0 as u32)?; + self.port.write(X0_HI, (x0 >> 32) as u32) + } + + /// The draw the next cell would take, after a sampling job. + pub fn next_draw(&mut self) -> Result { + let lo = self.port.read(DRAW_LO)? as u64; + let hi = self.port.read(DRAW_HI)? as u64; + Ok((hi << 32) | lo) + } + + pub fn set_boundary(&mut self, s: &[i8]) -> Result<(), Error> { + if s.len() != self.n { + return err(format!("a start of {} cells for {} variables", s.len(), self.n)); + } + for (k, &word) in encode(s).iter().enumerate() { + self.port.write(BOUNDARY + k as u16, word)?; + } + Ok(()) + } + + pub fn read_state(&mut self) -> Result, Error> { + let need = (2 * self.n).div_ceil(32); + let mut w = [0u32; 4]; + for (k, word) in w.iter_mut().enumerate().take(need) { + *word = self.port.read(STATE + k as u16)?; + } + decode(&w, self.n) + } + + /// The field the engine computes for cell `i` at its current state. + pub fn field(&mut self, i: usize) -> Result { + self.port.write(SEL, i as u32)?; + Ok(self.port.read(FIELD)? as i32 as i64) + } + + pub fn load_state(&mut self, s: &[i8]) -> Result<(), Error> { + self.set_boundary(s)?; + self.port.write(CONTROL, CTL_LOADSTATE) + } + + fn finish(&mut self) -> Result<(), Error> { + self.port.wait_idle(self.timeout)?; + let st = self.port.read(STATUS)?; + if st & 2 == 0 { + return err(format!("the job stopped without finishing (status {st:#x})")); + } + Ok(()) + } + + /// The settle kernel: every cell at once, to a fixed point, a two-cycle or the bound. + pub fn run_t0(&mut self, start: &[i8], bound: u32) -> Result { + self.port.write(MODE, MODE_T0)?; + self.port.write(BOUND, bound)?; + self.set_boundary(start)?; + self.port.write(CONTROL, CTL_CLRCNT)?; + self.port.write(CONTROL, CTL_START)?; + self.finish()?; + let term = (self.port.read(STATUS)? >> 2) & 3; + let steps = self.port.read(STEPS)?; + let cycles = self.counter(CYCLES)?; + Ok(T0Run { state: self.read_state()?, steps, term, cycles }) + } + + /// `sweeps` sweeps of `mode` from `start` with the draws of the counter set by [`Self::chain`], + /// the level stepping every `spl` sweeps, then one synchronous T=0 sweep if `final_t0`. + pub fn run_sweeps(&mut self, mode: u32, start: &[i8], sweeps: u32, spl: u32, final_t0: bool) -> Result { + self.prepare(mode, start, sweeps, spl, final_t0)?; + self.port.write(CONTROL, CTL_CLRCNT)?; + self.port.write(CONTROL, CTL_START)?; + self.finish()?; + self.sweep_result() + } + + /// More sweeps from the current state, the level back to 0 and the draws continuing: with a + /// fresh table this is how a schedule longer than the table runs. + pub fn cont_sweeps(&mut self, mode: u32, sweeps: u32, spl: u32, final_t0: bool) -> Result { + if sweeps == 0 { + return err("CONT needs at least one sweep"); + } + self.port.write(MODE, mode | if final_t0 { MODE_FINAL_T0 } else { 0 })?; + self.port.write(BOUND, sweeps)?; + self.port.write(SPL, spl.max(1))?; + self.port.write(CONTROL, CTL_CLRCNT)?; + self.port.write(CONTROL, CTL_CONT)?; + self.finish()?; + self.sweep_result() + } + + fn sweep_result(&mut self) -> Result { + let sweeps = self.port.read(STEPS)?; + let cycles = self.counter(CYCLES)?; + let updates = self.counter(UPDATES)?; + Ok(SweepRun { state: self.read_state()?, sweeps, cycles, updates }) + } + + pub fn prepare(&mut self, mode: u32, start: &[i8], sweeps: u32, spl: u32, final_t0: bool) -> Result<(), Error> { + self.port.write(MODE, mode | if final_t0 { MODE_FINAL_T0 } else { 0 })?; + self.port.write(BOUND, sweeps)?; + self.port.write(SPL, spl.max(1))?; + self.set_boundary(start) + } + + /// Repeat mode, for power: the job restarts from its start every time it ends, until + /// [`Self::halt`]. A sampling job reloads its counter, so every repeat is the same chain. + pub fn run_repeat(&mut self, mode: u32, bound: u32, spl: u32, final_t0: bool) -> Result<(), Error> { + self.port.write(MODE, mode | MODE_REPEAT | if final_t0 { MODE_FINAL_T0 } else { 0 })?; + self.port.write(BOUND, bound)?; + self.port.write(SPL, spl.max(1))?; + self.port.write(CONTROL, CTL_CLRCNT)?; + self.port.write(CONTROL, CTL_START) + } + + pub fn halt(&mut self) -> Result<(), Error> { + self.port.write(CONTROL, CTL_STOP)?; + let m = self.port.read(MODE)?; + self.port.write(MODE, m & !MODE_REPEAT) + } +} diff --git a/wai/wai-quantum-fpga/src/lib.rs b/wai/wai-quantum-fpga/src/lib.rs new file mode 100644 index 000000000..89ad353e4 --- /dev/null +++ b/wai/wai-quantum-fpga/src/lib.rs @@ -0,0 +1,20 @@ +//! An FPGA board as a member of `wai.quantum.landscape/1`: the settle and sample kernels of the +//! energy-landscape job in programmable logic (`rtl/`), the register-level model the logic is +//! tested against, and the host that runs jobs on the board, holds it to the conformance set, +//! and meters its power. Classical throughout: three-state cells and a counter-based random +//! stream, no quantum effect. + +pub mod conform; +pub mod engine; +pub mod member; +pub mod model; +pub mod port; +pub mod regs; +pub mod script; + +#[cfg(unix)] +pub mod board; +#[cfg(unix)] +pub mod emio; +#[cfg(unix)] +pub mod power; diff --git a/wai/wai-quantum-fpga/src/main.rs b/wai/wai-quantum-fpga/src/main.rs new file mode 100644 index 000000000..d7c7b8a86 --- /dev/null +++ b/wai/wai-quantum-fpga/src/main.rs @@ -0,0 +1,337 @@ +//! wai-quantum-fpga: the command line. Board commands run on the board's own Linux, as root (for +//! /dev/mem). `selftest`, `gen-tb` and `analyze` run anywhere. + +use serde_json::Value; +use std::fmt::Write as _; +use std::process::exit; +use wai_quantum::quantum_landscape::energy::Landscape; +use wai_quantum::quantum_landscape::job; +use wai_quantum_fpga::conform::{self, Report}; +use wai_quantum_fpga::engine::{Engine, Loaded}; +use wai_quantum_fpga::member::{BoardInfo, BoardMember, lowered, plan}; +use wai_quantum_fpga::model::ModelPort; +use wai_quantum_fpga::port::{Error, Port}; +use wai_quantum_fpga::regs::*; +use wai_quantum_fpga::script::ScriptPort; + +fn usage() -> ! { + eprintln!( + "usage: wai-quantum-fpga \n\ + \x20 selftest [--scale X] [--seeds N] every check against the register-level model (no board)\n\ + \x20 gen-tb the RTL testbench's script, recorded from the model\n\ + \x20 analyze [--predict mW] a power log, summarised\n\ + on the board, as root:\n\ + \x20 load load the bitstream, prove the load, check the engine answers\n\ + \x20 status engine, clock and power\n\ + \x20 conform [--scale X] the conformance set, random settles, field read-back\n\ + \x20 agree [--seeds N] every worked example, board against the CPU reference\n\ + \x20 landscape [--key FILE | --demo-key | --unsealed]\n\ + \x20 a job of wai.quantum.landscape/1 on the board, sealed\n\ + \x20 throughput clocks, jobs and operations a second, from the counters\n\ + \x20 meter [--predict mW] [--null] [--cores K]\n\ + \x20 run/halt board power; classes t0-recall gibbs-recall t0-idle\n\ + \x20 clock pl_clk0 = 1500/div0 MHz, measured back by the engine\n\ + \x20 release the EMIO banks back as the board boots\n\ + board commands take --core C on the multi-core bitstream" + ); + exit(2) +} + +fn flag(args: &[String], name: &str) -> Option { + args.iter().position(|a| a == name).and_then(|i| args.get(i + 1)).and_then(|v| v.parse().ok()) +} + +fn die(e: impl std::fmt::Display) -> ! { + eprintln!("wai-quantum-fpga: {e}"); + exit(1) +} + +fn finish(rep: &Report) -> ! { + print!("{}", rep.text); + println!("{} checks, {} failed", rep.checks, rep.failures.len()); + exit(if rep.failures.is_empty() { 0 } else { 1 }) +} + +/// The dense recall landscape the power classes run: 8 stored patterns on 64 cells, and a cue 30% +/// corrupted. +fn recall() -> (Landscape, Vec) { + let mut z = 0x7EC4u64; + let mut next = || { + z = z.wrapping_add(GOLDEN); + let mut r = z; + r = (r ^ (r >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); + r = (r ^ (r >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); + r ^ (r >> 31) + }; + let pats: Vec> = (0..8).map(|_| (0..64).map(|_| if next() & 1 == 0 { 1 } else { -1 }).collect()).collect(); + let mut l = Landscape::new(64); + for i in 0..64 { + for j in (i + 1)..64 { + let s: i64 = pats.iter().map(|p| p[i] * p[j]).sum(); + if s != 0 { + l.couple(i, j, s.signum() as i8); + } + } + } + let mut cue: Vec = pats[0].iter().map(|&x| x as i8).collect(); + for _ in 0..19 { + let i = (next() % 64) as usize; + cue[i] = -cue[i]; + } + (l, cue) +} + +fn model_member() -> BoardMember { + let mut m = BoardMember::new(Engine::open(ModelPort::new()).unwrap_or_else(|e| die(e)), BoardInfo { name: "model".into(), description: "the register-level model of the engine, in software".into() }); + let t = m.earn().unwrap_or_else(|e| die(e)); + print!("{t}"); + m +} + +fn key(args: &[String]) -> Option { + use wai_quantum::ed25519_dalek::SigningKey; + if args.iter().any(|a| a == "--unsealed") { + return None; + } + if args.iter().any(|a| a == "--demo-key") { + return Some(SigningKey::from_bytes(&[42; 32])); + } + let path = flag::(args, "--key") + .or_else(|| std::env::var("WAI_QUANTUM_KEY").ok()) + .unwrap_or_else(|| format!("{}/.config/wai/quantum-key", std::env::var("HOME").unwrap_or_else(|_| "/root".into()))); + let text = match std::fs::read_to_string(&path) { + Ok(t) => t, + Err(_) => { + let mut seed = [0u8; 32]; + use std::io::Read; + std::fs::File::open("/dev/urandom").and_then(|mut f| f.read_exact(&mut seed)).unwrap_or_else(|e| die(format!("no random source for a key: {e}"))); + let t: String = seed.iter().map(|b| format!("{b:02x}")).collect(); + if let Some(d) = std::path::Path::new(&path).parent() { + let _ = std::fs::create_dir_all(d); + } + #[cfg(unix)] + { + use std::io::Write; + use std::os::unix::fs::OpenOptionsExt; + std::fs::OpenOptions::new().write(true).create_new(true).mode(0o600).open(&path).and_then(|mut f| f.write_all(format!("{t}\n").as_bytes())).unwrap_or_else(|e| die(format!("{path}: {e}"))); + } + eprintln!("made a signing key at {path}"); + t + } + }; + let t = text.trim(); + let mut seed = [0u8; 32]; + if t.len() != 64 { + die(format!("{path}: a key file holds 64 hex digits")); + } + for (i, b) in seed.iter_mut().enumerate() { + *b = u8::from_str_radix(&t[2 * i..2 * i + 2], 16).unwrap_or_else(|_| die(format!("{path}: not hex"))); + } + Some(SigningKey::from_bytes(&seed)) +} + +/// The board commands, on any port: the whole board, or one core of the multi-core bitstream. +fn on_board(cmd: &str, args: &[String], port: P) { + let engine = Engine::open(port).unwrap_or_else(|e| die(e)); + let mut m = BoardMember::new(engine, BoardInfo::default()); + match cmd { + "conform" => { + let mut rep = conform::full(&m, flag(args, "--scale").unwrap_or(1.0)).unwrap_or_else(|e| die(e)); + let f = conform::fields(&m, 64).unwrap_or_else(|e| die(e)); + rep.text += &f.text; + rep.checks += f.checks; + rep.failures.extend(f.failures); + let ok = m.engine.borrow_mut().port.read(ID).map(|v| v == ID_VALUE).unwrap_or(false); + rep.check(ok, "the engine still answers with its ID at the end: no other session replaced it"); + finish(&rep) + } + "agree" => { + let t = m.earn().unwrap_or_else(|e| die(e)); + print!("{t}"); + println!("parity earned on this board: settle {}, sample {}", m.settle_parity.label(), m.sample_parity.label()); + let rep = conform::agree(&m, flag(args, "--seeds").unwrap_or(200)).unwrap_or_else(|e| die(e)); + finish(&rep) + } + "landscape" => { + let op = args.get(1).cloned().unwrap_or_else(|| usage()); + let file = args.get(2).cloned().unwrap_or_else(|| usage()); + let text = if file == "-" { + let mut s = String::new(); + std::io::Read::read_to_string(&mut std::io::stdin(), &mut s).unwrap_or_else(|e| die(e)); + s + } else { + std::fs::read_to_string(&file).unwrap_or_else(|e| die(e)) + }; + let request: Value = serde_json::from_str(&text).unwrap_or_else(|e| die(format!("the request is not JSON: {e}"))); + m.earn().unwrap_or_else(|e| die(e)); + let answer = match job::call_on(&m, &op, &request) { + Ok(a) => a, + Err(r) => die(r.plain()), + }; + let out = match key(args) { + Some(k) if op != "examples" => job::seal(&request, answer, &k), + _ => answer, + }; + println!("{}", serde_json::to_string_pretty(&out).unwrap()); + } + "status" => { + let mut e = m.engine.borrow_mut(); + let t0 = std::time::Instant::now(); + let u0 = e.counter(UPTIME).unwrap_or_else(|x| die(x)); + std::thread::sleep(std::time::Duration::from_millis(500)); + let u1 = e.counter(UPTIME).unwrap_or_else(|x| die(x)); + println!("engine answers: ID and version {VERSION_VALUE:#010x}; measured clock {:.3} MHz", (u1 - u0) as f64 / t0.elapsed().as_secs_f64() / 1e6); + } + "throughput" => { + let (l, cue) = recall(); + let w = l.lower(); + let lw = lowered(&w); + let seg = plan(&lw, &[0.25]).unwrap_or_else(|x| die(x)).remove(0); + let mut e = m.engine.borrow_mut(); + e.load(&lw).unwrap_or_else(|x| die(x)); + e.load_table(seg.win_lo, seg.wbits, seg.first, &seg.levels).unwrap_or_else(|x| die(x)); + e.chain(1).unwrap_or_else(|x| die(x)); + for (name, mode, bound) in [("settle, dense 64 (recall cue)", MODE_T0, 60u32), ("sample, sequential, 64 cells, 8 sweeps at T = 0.25", MODE_GIBBS, 8)] { + e.set_boundary(&cue).unwrap_or_else(|x| die(x)); + e.run_repeat(mode, bound, 8, false).unwrap_or_else(|x| die(x)); + std::thread::sleep(std::time::Duration::from_millis(200)); + let (t0, j0, u0, c0, y0) = (std::time::Instant::now(), e.counter(JOBS).unwrap(), e.counter(UPDATES).unwrap(), e.counter(UPTIME).unwrap(), e.counter(CYCLES).unwrap()); + std::thread::sleep(std::time::Duration::from_secs(2)); + let (dt, j1, u1, c1, y1) = (t0.elapsed().as_secs_f64(), e.counter(JOBS).unwrap(), e.counter(UPDATES).unwrap(), e.counter(UPTIME).unwrap(), e.counter(CYCLES).unwrap()); + e.halt().unwrap(); + let jobs = (j1 - j0) as f64 / dt; + let acc = if mode == MODE_T0 { w.settle(&cue, bound).accumulates as f64 * jobs } else { jobs * f64::from(bound) * lw.nonzeros() as f64 }; + println!("{name}: clock {:.3} MHz, {:.1} clocks a job, {jobs:.0} jobs/s, {:.3e} cell updates/s, {acc:.3e} accumulates/s", (c1 - c0) as f64 / dt / 1e6, (y1 - y0) as f64 / (j1 - j0).max(1) as f64, (u1 - u0) as f64 / dt); + } + } + _ => usage(), + } +} + +#[cfg(unix)] +fn meter_class(e: &mut Engine

, class: &str, plan_m: &wai_quantum_fpga::power::Plan, log: &mut String) -> Vec { + let (l, cue) = recall(); + let lw = lowered(&l.lower()); + let seg = plan(&lw, &[0.25]).unwrap_or_else(|x| die(x)).remove(0); + let class = class.to_string(); + let mut start = move |e: &mut Engine

| -> Result<(), Error> { + match class.as_str() { + "t0-recall" => { + e.load(&lw)?; + e.set_boundary(&cue)?; + e.run_repeat(MODE_T0, 60, 1, false) + } + "gibbs-recall" => { + e.load(&lw)?; + e.load_table(seg.win_lo, seg.wbits, seg.first, &seg.levels)?; + e.chain(1)?; + e.set_boundary(&cue)?; + e.run_repeat(MODE_GIBBS, 8, 8, true) + } + "t0-idle" => { + e.load(&Loaded { n: 64, rows: vec![vec![]; 64], fields: vec![0; 64], cost: 0 })?; + e.set_boundary(&[0; 64])?; + e.run_repeat(MODE_T0, 0, 1, false) + } + other => Err(Error(format!("unknown class {other}"))), + } + }; + wai_quantum_fpga::power::meter(e, plan_m, &mut start, log).unwrap_or_else(|x| die(x)) +} + +fn main() { + let args: Vec = std::env::args().skip(1).collect(); + let cmd = args.first().map(|s| s.as_str()).unwrap_or_else(|| usage()); + match cmd { + "selftest" => { + let scale = flag(&args, "--scale").unwrap_or(0.02); + let m = model_member(); + let mut rep = conform::full(&m, scale).unwrap_or_else(|e| die(e)); + for r in [conform::fields(&m, 4).unwrap_or_else(|e| die(e)), conform::agree(&m, flag(&args, "--seeds").unwrap_or(3)).unwrap_or_else(|e| die(e))] { + rep.text += &r.text; + rep.checks += r.checks; + rep.failures.extend(r.failures); + } + finish(&rep) + } + "gen-tb" => { + let out = args.get(1).unwrap_or_else(|| usage()); + let m = BoardMember::new(Engine::open(ScriptPort::new()).unwrap_or_else(|e| die(e)), BoardInfo::default()); + conform::tb_session(&m).unwrap_or_else(|e| die(e)); + let e = m.engine.borrow(); + std::fs::write(out, &e.port.text).unwrap_or_else(|e| die(e)); + println!("{} lines, {} checked reads -> {out}", e.port.text.lines().count(), e.port.reads); + } + "analyze" => { + let f = args.get(1).unwrap_or_else(|| usage()); + let text = std::fs::read_to_string(f).unwrap_or_else(|e| die(e)); + #[cfg(unix)] + { + let pts = wai_quantum_fpga::power::parse_log(&text); + print!("{}", wai_quantum_fpga::power::summarise(&pts, flag(&args, "--predict")).text); + } + let _ = text; + } + #[cfg(unix)] + "load" => { + let f = args.get(1).unwrap_or_else(|| usage()); + println!("{}", wai_quantum_fpga::board::load_bitstream(f).unwrap_or_else(|e| die(e))); + let _l = wai_quantum_fpga::board::lock().unwrap_or_else(|e| die(e)); + let port = wai_quantum_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e)); + Engine::open(port).unwrap_or_else(|e| die(e)); + println!("the engine answers with its ID and version"); + } + #[cfg(unix)] + "release" => { + wai_quantum_fpga::emio::EmioPort::release_to_boot_state().unwrap_or_else(|e| die(e)); + println!("EMIO banks 3-5: inputs, outputs disabled, data 0"); + } + #[cfg(unix)] + "clock" => { + let d: u32 = args.get(1).and_then(|v| v.parse().ok()).unwrap_or_else(|| usage()); + let _l = wai_quantum_fpga::board::lock().unwrap_or_else(|e| die(e)); + let mut e = Engine::open(wai_quantum_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e))).unwrap_or_else(|e| die(e)); + let old = wai_quantum_fpga::board::set_pl0_div(d).unwrap_or_else(|e| die(e)); + std::thread::sleep(std::time::Duration::from_millis(100)); + let t0 = std::time::Instant::now(); + let u0 = e.counter(UPTIME).unwrap_or_else(|e| die(e)); + std::thread::sleep(std::time::Duration::from_secs(1)); + let u1 = e.counter(UPTIME).unwrap_or_else(|e| die(e)); + println!("PL0_REF_CTRL was {old:#x}; nominal {:.2} MHz; measured {:.4} MHz", 1500.0 / f64::from(d), (u1 - u0) as f64 / t0.elapsed().as_secs_f64() / 1e6); + } + #[cfg(unix)] + "meter" => { + let class = args.get(1).cloned().unwrap_or_else(|| usage()); + let passes: u32 = args.get(2).and_then(|v| v.parse().ok()).unwrap_or_else(|| usage()); + let settle_s: u64 = args.get(3).and_then(|v| v.parse().ok()).unwrap_or_else(|| usage()); + let samples: u32 = args.get(4).and_then(|v| v.parse().ok()).unwrap_or_else(|| usage()); + let plan_m = wai_quantum_fpga::power::Plan { passes, settle_s, samples, null: args.iter().any(|a| a == "--null") }; + let cores: u16 = flag(&args, "--cores").unwrap_or(1); + let _l = wai_quantum_fpga::board::lock().unwrap_or_else(|e| die(e)); + let port = wai_quantum_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e)); + let mut log = String::new(); + let _ = writeln!(log, "# class {class} cores {cores}"); + let pts = if cores > 1 { + let mut e = Engine::open(wai_quantum_fpga::port::BroadcastPort { inner: port, k: cores }).unwrap_or_else(|x| die(x)); + meter_class(&mut e, &class, &plan_m, &mut log) + } else { + let mut e = Engine::open(port).unwrap_or_else(|x| die(x)); + meter_class(&mut e, &class, &plan_m, &mut log) + }; + print!("{log}"); + for l in wai_quantum_fpga::power::summarise(&pts, flag(&args, "--predict")).text.lines() { + println!("# {l}"); + } + } + #[cfg(unix)] + "conform" | "agree" | "landscape" | "status" | "throughput" => { + let _l = wai_quantum_fpga::board::lock().unwrap_or_else(|e| die(e)); + let mut port = wai_quantum_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e)); + match flag::(&args, "--core") { + Some(core) => on_board(cmd, &args, wai_quantum_fpga::port::CorePort { inner: &mut port, core }), + None => on_board(cmd, &args, port), + } + } + _ => usage(), + } +} diff --git a/wai/wai-quantum-fpga/src/member.rs b/wai/wai-quantum-fpga/src/member.rs new file mode 100644 index 000000000..dc05c7d63 --- /dev/null +++ b/wai/wai-quantum-fpga/src/member.rs @@ -0,0 +1,248 @@ +//! The board as a member of `wai.quantum.landscape/1` (`extensions/quantum-landscape.md` §5): it +//! runs the two kernels and nothing else. The reader, the truth, the energy of what it returns, the +//! verdict, the replay digest and the seal stay on the host (`wai_quantum::quantum_landscape`). +//! +//! **The settle kernel** is the engine's T0 mode, whole-number arithmetic, so it gives the +//! reference's bits by construction; the conformance set's `settle` vectors hold it to them. +//! +//! **The sample kernel** takes the reference's draws: the engine counts `chain + j·GOLDEN` and mixes +//! it in logic, so the draw of cell `i` in sweep `k` is the top 53 bits of `out(chain, k·n + i + 1)`, +//! the reference's. The host gives it the cut points, computed with the reference's `cuts`, so the +//! fabric needs no exponential: per temperature level, a window of fields from `WIN_LO`. A field +//! outside the window takes the threshold, which equals the reference's draw only where that draw is +//! certain, so the host puts the window over every field a cell can reach whose draw is not certain +//! at any of the segment's temperatures, and refuses a job for which no window fits. The `chains` +//! vectors hold the result to the reference's bits. +//! +//! A run longer than the table holds goes in segments: the table is rewritten and the run continued +//! with the draws running on. A chain at temperature 0 runs every sweep, as the reference's does: +//! the engine stops at a fixed point or a two-cycle, and past a two-cycle the state alternates, so +//! one more sweep is taken when the count left is odd. +//! +//! What it has earned is not assumed: [`BoardMember::earn`] runs the `settle` and `chains` vectors +//! on the device, and a kernel that misses one bit is `uncertified`. + +use crate::engine::{Engine, Loaded}; +use crate::port::Port; +use crate::regs::*; +use std::cell::RefCell; +use wai_quantum::quantum_landscape::member::{Chain, KERNEL_SAMPLE, KERNEL_SETTLE, Member, Parity}; +use wai_quantum::quantum_landscape::sample::{cuts, temperature}; +use wai_quantum::quantum_landscape::settle::{Outcome, Term, Workload, threshold}; + +/// The conformance set a member is held to, as the repository holds it. +pub const VECTORS: &str = include_str!("../../quantum-conformance/landscape/vectors.json"); + +/// What the board is, as data: the member's name and its answers' label carry it. +#[derive(Clone, Debug)] +pub struct BoardInfo { + /// A short board name for the member id, `fpga:-landscape-64`. + pub name: String, + /// The board and part, in words, for the label. + pub description: String, +} + +impl Default for BoardInfo { + fn default() -> Self { + BoardInfo { name: "kv260".into(), description: "a KV260 board (xck26 programmable logic, four Cortex-A53 cores)".into() } + } +} + +pub struct BoardMember { + pub engine: RefCell>, + pub info: BoardInfo, + pub settle_parity: Parity, + pub sample_parity: Parity, +} + +/// One segment of a sampling chain: up to `levels.len()` sweeps on one table. +#[derive(Clone, Debug)] +pub struct Segment { + pub sweeps: usize, + pub win_lo: i64, + pub wbits: u32, + pub first: i64, + pub levels: Vec>, +} + +fn certain(h: i64, cost: i64) -> (u64, u64) { + match threshold(h, cost) { + 1 => (0, 0), + -1 => (TWO53, TWO53), + _ => (0, TWO53), + } +} + +/// The workload as the engine holds it. +pub fn lowered(w: &Workload) -> Loaded { + let n = w.n(); + let zero = vec![0i8; n]; + Loaded { + n, + rows: (0..n).map(|i| w.row(i).map(|(j, v)| (j, v as i8)).collect()).collect(), + fields: (0..n).map(|i| w.field(&zero, i)).collect(), + cost: w.cost(), + } +} + +/// The fields cell `i` can reach: its own field plus or minus its number of couplings. +fn reach(l: &Loaded) -> Vec { + let mut hs: Vec = Vec::new(); + for i in 0..l.n { + let d = l.rows[i].len() as i64; + hs.extend((l.fields[i] - d)..=(l.fields[i] + d)); + } + hs.sort_unstable(); + hs.dedup(); + hs +} + +/// The tables for a run at temperatures `temps` (each above 0), one segment per table. +pub fn plan(l: &Loaded, temps: &[f64]) -> Result, String> { + let hs = reach(l); + let (lo_r, hi_r) = (*hs.first().unwrap_or(&0), *hs.last().unwrap_or(&0)); + let mut out = Vec::new(); + let mut k = 0; + while k < temps.len() { + let mut take = (temps.len() - k).min(LEVELS); + loop { + let betas: Vec = temps[k..k + take].iter().map(|t| 1.0 / t).collect(); + // the fields whose draw is not certain at some temperature of the segment + let open: Vec = hs.iter().copied().filter(|&h| betas.iter().any(|&b| cuts(h, l.cost, b) != certain(h, l.cost))).collect(); + let (lo, hi) = match (open.first(), open.last()) { + (Some(&a), Some(&b)) => (a, b), + _ => (lo_r, lo_r), + }; + let span = (hi - lo + 1) as u64; + let wbits = 64 - (span - 1).leading_zeros().min(64); + let wbits = if span <= 1 { 0 } else { wbits }; + if wbits > MAX_WBITS { + return Err(format!( + "the fields whose draw is uncertain run from {lo} to {hi}, {span} of them, and the engine's table holds {TABLE} a temperature" + )); + } + let fit = (TABLE >> wbits).min(LEVELS); + if take > fit { + take = fit; + continue; + } + // every reachable field inside the window gets its exact pair + let (first, last) = (lo.max(lo_r), (lo + (1i64 << wbits) - 1).min(hi_r)); + let levels = betas.iter().map(|&b| if first <= last { (first..=last).map(|h| cuts(h, l.cost, b)).collect() } else { Vec::new() }).collect(); + out.push(Segment { sweeps: take, win_lo: lo, wbits, first, levels }); + break; + } + k += take; + } + Ok(out) +} + +impl BoardMember

{ + pub fn new(engine: Engine

, info: BoardInfo) -> Self { + BoardMember { engine: RefCell::new(engine), info, settle_parity: Parity::Uncertified, sample_parity: Parity::Uncertified } + } + + /// Run the conformance set's `settle` and `chains` vectors on the device, and take the parity + /// they earn: bit-exact if every one gives the reference's bits, else uncertified. + pub fn earn(&mut self) -> Result { + let r = crate::conform::bits(self)?; + self.settle_parity = if r.settle_ok { Parity::BitExact } else { Parity::Uncertified }; + self.sample_parity = if r.chains_ok { Parity::BitExact } else { Parity::Uncertified }; + Ok(r.text) + } + + fn load(&self, w: &Workload) -> Result { + let l = lowered(w); + self.engine.borrow_mut().load(&l).map_err(|e| e.0)?; + Ok(l) + } + + /// The chain at temperature 0, every sweep run. + fn quench(&self, w: &Workload, start: &[i8], sweeps: u32) -> Result, String> { + if sweeps == 0 { + return Ok(start.to_vec()); + } + let mut e = self.engine.borrow_mut(); + let r = e.run_t0(start, sweeps).map_err(|e| e.0)?; + if r.term == TERM_2C && (sweeps - r.steps) % 2 == 1 { + return Ok(e.run_t0(&r.state, 1).map_err(|e| e.0)?.state); + } + let _ = w; + Ok(r.state) + } +} + +impl Member for BoardMember

{ + fn id(&self) -> String { + format!("fpga:{}-landscape-64", self.info.name) + } + + fn class(&self) -> &'static str { + "fpga" + } + + fn parity(&self, kernel: &str) -> Parity { + match kernel { + KERNEL_SETTLE => self.settle_parity, + KERNEL_SAMPLE => self.sample_parity, + _ => Parity::Uncertified, + } + } + + fn label(&self) -> String { + format!( + "A classical FPGA board running the settling model's two kernels: {}, up to 64 three-state cells in programmable logic, driven by the board's own processor. Energies are computed exactly on the host. The joule line is priced, as on every member; a reading of the board's power, where one is taken, stands beside it. No photonic hardware, custom chip or quantum effect is involved in this run.", + self.info.description + ) + } + + fn settle(&self, w: &Workload, start: &[i8], bound: u32) -> Result { + let l = self.load(w)?; + let r = self.engine.borrow_mut().run_t0(start, bound).map_err(|e| e.0)?; + let term = match r.term { + TERM_FP => Term::FixedPoint, + TERM_2C => Term::TwoCycle, + _ => Term::Bound, + }; + Ok(Outcome { state: r.state, sweeps: r.steps, term, accumulates: u64::from(r.steps) * l.nonzeros(), draws: 0 }) + } + + fn chains(&self, w: &Workload, chains: &[Chain], t0: f64, sweeps: u32) -> Result, String> { + let l = self.load(w)?; + let n = l.n; + let hot = if t0 > 0.0 && sweeps > 1 { sweeps - 1 } else { 0 }; + let temps: Vec = (0..hot).map(|k| temperature(t0, sweeps, k)).collect(); + if temps.iter().any(|&t| t <= 0.0) { + return Err("a sweep before the last came out at temperature 0".into()); + } + let segments = if hot > 0 { plan(&l, &temps)? } else { Vec::new() }; + let mut out = Vec::with_capacity(chains.len()); + for c in chains { + if c.start.len() != n { + return Err(format!("a start of {} cells for {n} variables", c.start.len())); + } + let state = if hot == 0 { + self.quench(w, &c.start, sweeps)? + } else { + let mut e = self.engine.borrow_mut(); + e.chain(c.seed).map_err(|e| e.0)?; + let mut state = Vec::new(); + for (s, seg) in segments.iter().enumerate() { + let last = s + 1 == segments.len(); + e.load_table(seg.win_lo, seg.wbits, seg.first, &seg.levels).map_err(|e| e.0)?; + let r = if s == 0 { + e.run_sweeps(MODE_GIBBS, &c.start, seg.sweeps as u32, 1, last) + } else { + e.cont_sweeps(MODE_GIBBS, seg.sweeps as u32, 1, last) + } + .map_err(|e| e.0)?; + state = r.state; + } + state + }; + let term = if w.rests(&state) { Term::FixedPoint } else { Term::Bound }; + out.push(Outcome { state, sweeps, term, accumulates: u64::from(sweeps) * l.nonzeros(), draws: n as u64 * u64::from(hot) }); + } + Ok(out) + } +} diff --git a/wai/wai-quantum-fpga/src/model.rs b/wai/wai-quantum-fpga/src/model.rs new file mode 100644 index 000000000..7124fa631 --- /dev/null +++ b/wai/wai-quantum-fpga/src/model.rs @@ -0,0 +1,379 @@ +//! A software model of `rtl/landscape_core.v` at the register port: the same bits, the same draw +//! order, the same cycle counts. The testbench replays a recording of this model against the RTL +//! and compares every read, so where the two disagree it is the RTL or this file, and the test says +//! which register. Fields are computed from the raw bits exactly as the RTL's cone does. + +use crate::port::{Error, Port}; +use crate::regs::*; +use std::time::Duration; + +/// SplitMix64's mix(), as the draw pipeline computes it. +pub fn mix(mut x: u64) -> u64 { + x ^= x >> 30; + x = x.wrapping_mul(0xBF58_476D_1CE4_E5B9); + x ^= x >> 27; + x = x.wrapping_mul(0x94D0_49BB_1331_11EB); + x ^ (x >> 31) +} + +#[derive(Clone)] +pub struct ModelPort { + mode: u32, + rep: bool, + final_t0: bool, + n_m1: u32, + cost: i64, + bound: u32, + spl: u32, + levels_m1: u32, + boundary: [u32; 4], + rows: Vec<[u32; 4]>, + fields: [i64; 64], + state: [u32; 4], + prev: [u32; 4], + tbl: Vec<(u64, u64)>, + tbl_addr: u32, + wa: u64, + wb_lo: u32, + tbl_q: (u64, u64), + x0: u64, + x_next: u64, + primed: bool, + win_lo: i64, + wbits: u32, + sel: u32, + done: bool, + term: u32, + steps: u32, + uptime: u64, + cycles: u64, + updates: u64, + jobs: u64, + rd_hi: u32, +} + +impl Default for ModelPort { + fn default() -> Self { + ModelPort { + mode: 0, + rep: false, + final_t0: false, + n_m1: 0, + cost: 0, + bound: 0, + spl: 1, + levels_m1: 0, + boundary: [0; 4], + rows: vec![[0; 4]; 64], + fields: [0; 64], + state: [0; 4], + prev: [0; 4], + tbl: vec![(0, 0); TABLE], + tbl_addr: 0, + wa: 0, + wb_lo: 0, + tbl_q: (0, 0), + x0: 0, + x_next: 0, + primed: false, + win_lo: 0, + wbits: 7, + sel: 0, + done: false, + term: 0, + steps: 0, + uptime: 0, + cycles: 0, + updates: 0, + jobs: 0, + rd_hi: 0, + } + } +} + +#[inline] +fn bit(words: &[u32; 4], b: usize) -> u32 { + (words[b >> 5] >> (b & 31)) & 1 +} +#[inline] +fn set2(words: &mut [u32; 4], cell: usize, v: u32) { + let b = 2 * cell; + words[b >> 5] = (words[b >> 5] & !(3 << (b & 31))) | ((v & 3) << (b & 31)); +} + +fn sext(v: u64, bits: u32) -> i64 { + let s = 64 - bits; + ((v << s) as i64) >> s +} + +impl ModelPort { + pub fn new() -> Self { + Self::default() + } + + /// The cone: h_i = sum_j (p_j - n_j) + b_i on the raw bits. + fn field(&self, st: &[u32; 4], i: usize) -> i64 { + let w = &self.rows[i]; + let mut h = 0i64; + for j in 0..64 { + let (wp, wn) = (bit(w, 2 * j), bit(w, 2 * j + 1)); + let (sp, sn) = (bit(st, 2 * j), bit(st, 2 * j + 1)); + h += ((wp & sp) | (wn & sn)) as i64 - ((wp & sn) | (wn & sp)) as i64; + } + h + self.fields[i] + } + + fn t0_next(&self, st: &[u32; 4]) -> [u32; 4] { + let mut ns = [0u32; 4]; + for i in 0..64 { + let h = self.field(st, i); + set2(&mut ns, i, (((h < -self.cost) as u32) << 1) | (h > self.cost) as u32); + } + ns + } + + fn draw(&self) -> u64 { + mix(self.x_next) >> 11 + } + + /// One cell's update: the S_IDX, S_TBL and S_COMMIT clocks. + fn sample(&mut self, h: i64, level: u32) -> u32 { + let d = h - self.win_lo; + let w = 1i64 << self.wbits; + let in_win = d >= 0 && d < w; + let lbase = ((level as u64) << self.wbits) & 0x1FFF; + let idx = (lbase + (d as u64 & 0x1FFF)) & 0x1FFF; + self.tbl_q = self.tbl[idx as usize]; + let u = self.draw(); + let s = if in_win { + let (a, b) = self.tbl_q; + if u < a { 0b10 } else if u < b { 0b00 } else { 0b01 } + } else { + (((h < -self.cost) as u32) << 1) | (h > self.cost) as u32 + }; + self.x_next = self.x_next.wrapping_add(GOLDEN); + s + } + + fn run_job(&mut self, load: bool) { + self.done = false; + self.term = TERM_NONE; + let mut cyc = 0u64; + let sampling = self.mode == MODE_GIBBS || self.mode == MODE_JACOBI; + if load { + cyc += 1; // S_LOAD + self.state = self.boundary; + self.prev = self.boundary; + if sampling { + cyc += PRIME; // S_PRIME + self.x_next = self.x0; + self.primed = true; + } + } + let mut prev = self.prev; + let mut prev2 = [0u32; 4]; + self.steps = 0; + self.sel = 0; + if self.mode == MODE_T0 { + let (mut have1, mut have2) = (false, false); + loop { + cyc += 1; + if have1 && self.state == prev { + self.term = TERM_FP; + break; + } + if have2 && self.state == prev2 { + self.term = TERM_2C; + break; + } + if self.steps == self.bound { + self.term = TERM_BOUND; + break; + } + let ns = self.t0_next(&self.state); + prev2 = prev; + prev = self.state; + self.state = ns; + self.prev = prev; + self.steps += 1; + have2 = have1; + have1 = true; + } + } else { + let jac = self.mode == MODE_JACOBI; + let (mut level, mut spl_cnt) = (0u32, 0u32); + if self.bound != 0 { + loop { + for i in 0..=(self.n_m1 as usize) { + cyc += 4; + let h = self.field(&self.state, i); + let s = self.sample(h, level); + if jac { set2(&mut prev, i, s) } else { set2(&mut self.state, i, s) } + self.updates += 1; + } + if jac { + cyc += 1; + self.state = prev; + } + self.steps += 1; + if self.steps == self.bound { + break; + } + if spl_cnt + 1 == self.spl { + spl_cnt = 0; + if level != self.levels_m1 { + level += 1; + } + } else { + spl_cnt += 1; + } + } + } + if self.final_t0 { + cyc += 1; + self.prev = self.state; + self.state = self.t0_next(&self.state); + } else { + self.prev = prev; + } + } + cyc += 1; // S_END + self.cycles += cyc; + self.uptime += cyc + 16; + self.jobs += 1; + self.done = true; + } +} + +impl Port for ModelPort { + fn write(&mut self, addr: u16, val: u32) -> Result<(), Error> { + self.uptime += 8; + match addr { + CONTROL => { + if val & CTL_CLRCNT != 0 { + self.cycles = 0; + self.updates = 0; + self.jobs = 0; + } + if val & CTL_CLEARW != 0 { + self.rows = vec![[0; 4]; 64]; + self.fields = [0; 64]; + } + if val & CTL_LOADSTATE != 0 { + self.state = self.boundary; + self.prev = self.boundary; + } + if val & CTL_START != 0 { + if self.rep { + return Err(Error("the model does not run repeat mode".into())); + } + self.run_job(true); + } else if val & CTL_CONT != 0 { + if self.rep || self.bound == 0 { + return Err(Error("CONT needs repeat off and a bound above 0".into())); + } + self.run_job(false); + } + if val & CTL_STOP != 0 { + self.done = false; + } + } + MODE => { + self.mode = val & 3; + self.rep = val & MODE_REPEAT != 0; + self.final_t0 = val & MODE_FINAL_T0 != 0; + } + N_M1 => self.n_m1 = val & 63, + COST => self.cost = (val & 0xF_FFFF) as i64, + BOUND => self.bound = val, + SPL => self.spl = val & 0xFFFF, + LEVELS_M1 => self.levels_m1 = val & 63, + X0_LO => self.x0 = (self.x0 & !0xFFFF_FFFF) | val as u64, + X0_HI => self.x0 = (self.x0 & 0xFFFF_FFFF) | ((val as u64) << 32), + WIN_LO => self.win_lo = sext(val as u64 & 0x3F_FFFF, 22), + WBITS => self.wbits = val & 15, + SEL => self.sel = val & 63, + TBL_ADDR => { + self.tbl_addr = val & 0x1FFF; + self.tbl_q = self.tbl[self.tbl_addr as usize]; + } + TBL_A_LO => self.wa = (self.wa & !0xFFFF_FFFF) | val as u64, + TBL_A_HI => self.wa = (self.wa & 0xFFFF_FFFF) | (((val & 0x3F_FFFF) as u64) << 32), + TBL_B_LO => self.wb_lo = val, + TBL_B_HI => { + let b = (((val & 0x3F_FFFF) as u64) << 32) | self.wb_lo as u64; + self.tbl[self.tbl_addr as usize] = (self.wa, b); + self.tbl_addr = (self.tbl_addr + 1) & 0x1FFF; + } + a if (BOUNDARY..BOUNDARY + 4).contains(&a) => self.boundary[(a - BOUNDARY) as usize] = val, + a if (FIELDS..FIELDS + 64).contains(&a) => self.fields[(a - FIELDS) as usize] = sext(val as u64 & 0xF_FFFF, 20), + a if (W..W + 256).contains(&a) => { + let k = (a - W) as usize; + self.rows[k >> 2][k & 3] = val; + } + _ => {} + } + Ok(()) + } + + fn read(&mut self, addr: u16) -> Result { + self.uptime += 8; + let lo_hi = |v: u64, s: &mut u32| { + *s = (v >> 32) as u32 & 0xFFFF; + v as u32 + }; + Ok(match addr { + ID => ID_VALUE, + VERSION => VERSION_VALUE, + STATUS => (self.term << 2) | ((self.done as u32) << 1), + MODE => ((self.final_t0 as u32) << 9) | ((self.rep as u32) << 8) | self.mode, + N_M1 => self.n_m1, + COST => self.cost as u32, + BOUND => self.bound, + WIN_LO => self.win_lo as i32 as u32, + WBITS => self.wbits, + FIELD => (self.field(&self.state, self.sel as usize) as i32) as u32, + TBL_ADDR => self.tbl_addr, + TBL_A_LO => self.tbl_q.0 as u32, + TBL_A_HI => (self.tbl_q.0 >> 32) as u32 & 0x3F_FFFF, + TBL_B_LO => self.tbl_q.1 as u32, + TBL_B_HI => (self.tbl_q.1 >> 32) as u32 & 0x3F_FFFF, + DRAW_LO => { + let u = self.draw(); + self.rd_hi = (u >> 32) as u32; + u as u32 + } + DRAW_HI => self.rd_hi, + UPTIME => { let u = self.uptime; lo_hi(u, &mut self.rd_hi) } + CYCLES => { let u = self.cycles; lo_hi(u, &mut self.rd_hi) } + UPDATES => { let u = self.updates; lo_hi(u, &mut self.rd_hi) } + JOBS => { let u = self.jobs; lo_hi(u, &mut self.rd_hi) } + 0x0021 | 0x0023 | 0x0025 | 0x0027 => self.rd_hi, + STEPS => self.steps, + a if (STATE..STATE + 4).contains(&a) => self.state[(a - STATE) as usize], + _ => 0xDEAD_BEEF, + }) + } + + fn wait_idle(&mut self, _timeout: Duration) -> Result<(), Error> { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// The model's draws are the reference's: the engine's counter starts at chain + GOLDEN and + /// the j-th draw of a chain is out(chain, j) >> 11. + #[test] + fn the_draw_is_the_reference_s() { + use wai_quantum::quantum_landscape::sample::out; + let chain = 0x1234_5678_9ABC_DEF0u64; + let mut x = chain.wrapping_add(GOLDEN); + for j in 1..=1000u64 { + assert_eq!(mix(x), out(chain, j)); + x = x.wrapping_add(GOLDEN); + } + } +} diff --git a/wai/wai-quantum-fpga/src/port.rs b/wai/wai-quantum-fpga/src/port.rs new file mode 100644 index 000000000..6834abd26 --- /dev/null +++ b/wai/wai-quantum-fpga/src/port.rs @@ -0,0 +1,76 @@ +//! A register port. The board's is the EMIO lines (`emio.rs`); the model's is software (`model.rs`); +//! the testbench script records one (`script.rs`). Everything above this trait is shared by all three. + +use std::fmt; +use std::time::Duration; + +#[derive(Debug, Clone, PartialEq)] +pub struct Error(pub String); +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} +impl std::error::Error for Error {} +pub fn err(s: impl Into) -> Result { + Err(Error(s.into())) +} + +pub trait Port { + fn write(&mut self, addr: u16, val: u32) -> Result<(), Error>; + fn read(&mut self, addr: u16) -> Result; + /// Poll until the engine is idle. Errs on timeout rather than waiting forever. + fn wait_idle(&mut self, timeout: Duration) -> Result<(), Error>; +} + +/// One core of the multi-core bitstream (`rtl/landscape_multi.v`): address bits 15:13 pick it. +pub struct CorePort<'a, P: Port> { + pub inner: &'a mut P, + pub core: u16, +} + +impl Port for CorePort<'_, P> { + fn write(&mut self, addr: u16, val: u32) -> Result<(), Error> { + self.inner.write((self.core << 13) | (addr & 0x1FFF), val) + } + fn read(&mut self, addr: u16) -> Result { + self.inner.read((self.core << 13) | (addr & 0x1FFF)) + } + fn wait_idle(&mut self, timeout: Duration) -> Result<(), Error> { + self.inner.wait_idle(timeout) + } +} + +/// Every write goes to all `k` cores, reads come from core 0: the same job on every core at once. +/// For power, where K identical copies amplify a signal the one-core meter cannot resolve. +pub struct BroadcastPort { + pub inner: P, + pub k: u16, +} + +impl Port for BroadcastPort

{ + fn write(&mut self, addr: u16, val: u32) -> Result<(), Error> { + for c in 0..self.k { + self.inner.write((c << 13) | (addr & 0x1FFF), val)?; + } + Ok(()) + } + fn read(&mut self, addr: u16) -> Result { + self.inner.read(addr & 0x1FFF) + } + fn wait_idle(&mut self, timeout: Duration) -> Result<(), Error> { + self.inner.wait_idle(timeout) + } +} + +impl Port for &mut P { + fn write(&mut self, addr: u16, val: u32) -> Result<(), Error> { + (**self).write(addr, val) + } + fn read(&mut self, addr: u16) -> Result { + (**self).read(addr) + } + fn wait_idle(&mut self, timeout: Duration) -> Result<(), Error> { + (**self).wait_idle(timeout) + } +} diff --git a/wai/wai-quantum-fpga/src/power.rs b/wai/wai-quantum-fpga/src/power.rs new file mode 100644 index 000000000..ec8c71c57 --- /dev/null +++ b/wai/wai-quantum-fpga/src/power.rs @@ -0,0 +1,242 @@ +//! Run/halt power on one bitstream, by the board's written rules: +//! +//! * no reload between the two states, so placement, routing and clock tree are identical and the +//! difference is the job's dynamic power; +//! * the order of run and halt is randomised in every pass, so a monotonic drift cannot alias onto it; +//! * sampling happens on the board, in this process, with nothing else running: a bare read of the +//! sensor and a sleep (the PS is inside the measurement); +//! * every point checks for a reload by another session two ways: the engine's uptime counter must +//! have kept counting (a reload resets it, even of the same file), and the dmesg count of FPGA +//! writes must not have moved. A flagged pass is dropped, never averaged in; +//! * the statistic is the mean of per-pass differences of medians, its error the standard error +//! across passes (n = passes, not samples); +//! * a prediction is written to `predictions/` before the run and printed beside the result. + +use crate::board; +use crate::engine::Engine; +use crate::port::{Error, Port}; +use crate::regs::*; +use std::fmt::Write as _; +use std::time::{Duration, Instant, SystemTime}; + +pub struct Plan { + pub passes: u32, + pub settle_s: u64, + pub samples: u32, + /// "halt" for an A/A null control: both slots halted, one of them labelled run. + pub null: bool, +} + +pub struct Point { + pub pass: u32, + pub run: bool, + pub samples: Vec, // mW + pub jobs_per_s: f64, + pub updates_per_s: f64, + pub clock_hz: f64, + pub flagged: Option, +} + +fn urandom_bit() -> bool { + let mut b = [0u8; 1]; + use std::io::Read; + if let Ok(mut f) = std::fs::File::open("/dev/urandom") { + let _ = f.read_exact(&mut b); + } + b[0] & 1 == 1 +} + +/// `start` puts the engine into repeat mode on the prepared job; the meter halts it for the halt slot. +pub fn meter(edge: &mut Engine

, plan: &Plan, start: &mut dyn FnMut(&mut Engine

) -> Result<(), Error>, log: &mut String) -> Result, Error> { + let sensor = board::find_power_sensor()?; + let mut pts = vec![]; + let _ = writeln!(log, "# wai-quantum-fpga meter passes={} settle={}s samples={} null={} sensor={sensor} started={:?}", plan.passes, plan.settle_s, plan.samples, plan.null, SystemTime::now()); + let _ = writeln!(log, "# pass state sample power_uW"); + for pass in 1..=plan.passes { + let order = if urandom_bit() { [true, false] } else { [false, true] }; + for run in order { + if run && !plan.null { + start(edge)?; + } else { + edge.halt()?; + } + let w0 = board::dmesg_fpga_writes()?.len(); + let id0 = edge.port.read(ID)?; + let up0 = edge.counter(UPTIME)?; + std::thread::sleep(Duration::from_secs(plan.settle_s)); + // the window: counters read just before and just after, sensor only in between + let j0 = edge.counter(JOBS)?; + let u0 = edge.counter(UPDATES)?; + let c0 = edge.counter(UPTIME)?; + let t0 = Instant::now(); + let mut s = Vec::with_capacity(plan.samples as usize); + for _ in 0..plan.samples { + s.push(board::read_uw(&sensor)? as f64 / 1000.0); + std::thread::sleep(Duration::from_secs(1)); + } + let dt = t0.elapsed().as_secs_f64(); + let c1 = edge.counter(UPTIME)?; + let j1 = edge.counter(JOBS)?; + let u1 = edge.counter(UPDATES)?; + let id1 = edge.port.read(ID)?; + let w1 = board::dmesg_fpga_writes()?.len(); + let mut flagged = None; + if id0 != ID_VALUE || id1 != ID_VALUE { + flagged = Some(format!("ID {id0:#x}/{id1:#x}")); + } else if !(up0 <= c0 && c0 < c1) { + flagged = Some(format!("uptime went {up0} -> {c0} -> {c1}: the PL was reconfigured")); + } else if w1 != w0 { + flagged = Some(format!("dmesg FPGA writes {w0} -> {w1}")); + } + let label = if run { "run" } else { "halt" }; + for (k, v) in s.iter().enumerate() { + let _ = writeln!(log, "{pass} {label} {} {}", k + 1, (v * 1000.0).round() as u64); + } + let clock = (c1 - c0) as f64 / dt; + let _ = writeln!(log, "# point pass={pass} state={label} jobs_per_s={:.1} updates_per_s={:.1} clock_hz={clock:.0}{}", (j1 - j0) as f64 / dt, (u1 - u0) as f64 / dt, + flagged.as_ref().map(|f| format!(" FLAGGED {f}")).unwrap_or_default()); + pts.push(Point { pass, run, samples: s, jobs_per_s: (j1 - j0) as f64 / dt, updates_per_s: (u1 - u0) as f64 / dt, clock_hz: clock, flagged }); + } + } + edge.halt()?; + let _ = writeln!(log, "# done {:?}", SystemTime::now()); + Ok(pts) +} + +pub struct Summary { + pub text: String, + pub delta_mw: f64, + pub se_mw: f64, + pub passes_used: usize, + pub jobs_per_s: f64, + pub updates_per_s: f64, + pub clock_hz: f64, + pub p_run_mw: f64, +} + +fn excursions(s: &[f64]) -> usize { + let m = median(s); + let mad = median(&s.iter().map(|x| (x - m).abs()).collect::>()).max(5.0); + s.iter().filter(|x| (*x - m).abs() > 6.0 * mad).count() +} + +pub fn summarise(pts: &[Point], predicted_mw: Option) -> Summary { + let mut t = String::new(); + let passes: Vec = { + let mut v: Vec = pts.iter().map(|p| p.pass).collect(); + v.dedup(); + v + }; + let mut diffs = vec![]; + let mut dropped = vec![]; + let (mut jps, mut ups, mut clk, mut prun) = (vec![], vec![], vec![], vec![]); + let mut exc = 0; + for &p in &passes { + let r = pts.iter().find(|x| x.pass == p && x.run); + let h = pts.iter().find(|x| x.pass == p && !x.run); + if let (Some(r), Some(h)) = (r, h) { + if let Some(f) = r.flagged.as_ref().or(h.flagged.as_ref()) { + dropped.push(format!("pass {p}: {f}")); + continue; + } + exc += excursions(&r.samples) + excursions(&h.samples); + let d = median(&r.samples) - median(&h.samples); + diffs.push(d); + jps.push(r.jobs_per_s); + ups.push(r.updates_per_s); + clk.push(r.clock_hz); + prun.push(median(&r.samples)); + } + } + let (m, sd) = mean_sd(&diffs); + let se = sd / (diffs.len() as f64).sqrt(); + let pos = diffs.iter().filter(|&&d| d > 0.0).count(); + let _ = writeln!(t, "passes used {} of {} (dropped: {:?})", diffs.len(), passes.len(), dropped); + let _ = writeln!(t, "run - halt = {m:.1} +/- {se:.1} mW (per-pass sd {sd:.1}); {pos}/{} passes positive; {exc} sample excursions beyond 6 MAD", diffs.len()); + let _ = writeln!(t, "per-pass differences (mW): {}", diffs.iter().map(|d| format!("{d:.1}")).collect::>().join(" ")); + let (j, _) = mean_sd(&jps); + let (u, _) = mean_sd(&ups); + let (c, _) = mean_sd(&clk); + let (pr, _) = mean_sd(&prun); + let _ = writeln!(t, "board power while running {pr:.0} mW; jobs/s {j:.1}; updates/s {u:.1}; measured PL clock {:.3} MHz", c / 1e6); + if let Some(p) = predicted_mw { + let _ = writeln!(t, "pre-registered prediction {p:.1} mW: measured/predicted = {:.2}, {:.1} sigma from it", m / p, (m - p) / se); + } + Summary { text: t, delta_mw: m, se_mw: se, passes_used: diffs.len(), jobs_per_s: j, updates_per_s: u, clock_hz: c, p_run_mw: pr } +} + +/// Parses a meter log back into points (so a run can be re-analysed anywhere). +pub fn parse_log(text: &str) -> Vec { + let mut pts: Vec = vec![]; + // kv_runhalt.sh (the September known-answer vehicle's script) flags interference this way + let mut foreign: Vec = vec![]; + for line in text.lines() { + for tag in ["# FOREIGN-LOAD pass=", "# NOT-OURS pass="] { + if let Some(r) = line.strip_prefix(tag) { + if let Some(p) = r.split_whitespace().next().and_then(|t| t.parse().ok()) { + foreign.push(p); + } + } + } + } + for line in text.lines() { + if let Some(rest) = line.strip_prefix("# point ") { + let get = |k: &str| rest.split_whitespace().find_map(|t| t.strip_prefix(&format!("{k}="))).unwrap_or("").to_string(); + let pass: u32 = get("pass").parse().unwrap_or(0); + let run = get("state") == "run"; + if let Some(p) = pts.iter_mut().rev().find(|p| p.pass == pass && p.run == run) { + p.jobs_per_s = get("jobs_per_s").parse().unwrap_or(0.0); + p.updates_per_s = get("updates_per_s").parse().unwrap_or(0.0); + p.clock_hz = get("clock_hz").parse().unwrap_or(0.0); + if rest.contains("FLAGGED") { + p.flagged = Some(rest.split("FLAGGED").nth(1).unwrap_or("").trim().to_string()); + } + } + continue; + } + if line.starts_with('#') || line.trim().is_empty() { + continue; + } + let f: Vec<&str> = line.split_whitespace().collect(); + if f.len() < 4 { + continue; + } + let (Ok(pass), Ok(uw)) = (f[0].parse::(), f[3].parse::()) else { continue }; + let run = f[1] == "run"; + match pts.iter_mut().find(|p| p.pass == pass && p.run == run) { + Some(p) => p.samples.push(uw / 1000.0), + None => pts.push(Point { pass, run, samples: vec![uw / 1000.0], jobs_per_s: 0.0, updates_per_s: 0.0, clock_hz: 0.0, flagged: None }), + } + } + for p in pts.iter_mut() { + if foreign.contains(&p.pass) && p.flagged.is_none() { + p.flagged = Some("foreign FPGA load in this pass (kv_runhalt.sh)".into()); + } + } + pts +} + +pub fn mean_sd(v: &[f64]) -> (f64, f64) { + let n = v.len() as f64; + if v.is_empty() { + return (f64::NAN, f64::NAN); + } + let m = v.iter().sum::() / n; + if v.len() < 2 { + return (m, f64::NAN); + } + (m, (v.iter().map(|x| (x - m) * (x - m)).sum::() / (n - 1.0)).sqrt()) +} + +pub fn median(v: &[f64]) -> f64 { + let mut s = v.to_vec(); + s.sort_by(|a, b| a.partial_cmp(b).unwrap()); + let n = s.len(); + if n == 0 { + f64::NAN + } else if n % 2 == 1 { + s[n / 2] + } else { + 0.5 * (s[n / 2 - 1] + s[n / 2]) + } +} diff --git a/wai/wai-quantum-fpga/src/regs.rs b/wai/wai-quantum-fpga/src/regs.rs new file mode 100644 index 000000000..66b9bb24c --- /dev/null +++ b/wai/wai-quantum-fpga/src/regs.rs @@ -0,0 +1,69 @@ +//! The engine's register map. Word addresses; `rtl/landscape_core.v` is the other half of this file. + +pub const ID: u16 = 0x0000; +pub const ID_VALUE: u32 = 0x4C41_4E44; // "LAND" +pub const VERSION: u16 = 0x0001; +pub const VERSION_VALUE: u32 = 0x0040_4002; // 64 cells, 64 levels, rtl 2 +pub const STATUS: u16 = 0x0002; // [0] busy [1] done [3:2] term +pub const CONTROL: u16 = 0x0003; +pub const MODE: u16 = 0x0004; // [1:0] mode [8] repeat [9] final T0 sweep +pub const N_M1: u16 = 0x0005; +pub const COST: u16 = 0x0006; // 20 bits +pub const BOUND: u16 = 0x0007; // T0 sweep bound, or sweeps +pub const SPL: u16 = 0x0008; // sweeps per temperature level +pub const LEVELS_M1: u16 = 0x0009; +pub const X0_LO: u16 = 0x0010; // the draw counter's start, chain seed + GOLDEN +pub const X0_HI: u16 = 0x0011; +pub const WIN_LO: u16 = 0x0012; // the table window's first field, signed +pub const WBITS: u16 = 0x0013; // the window holds 2^WBITS fields +pub const SEL: u16 = 0x0014; +pub const FIELD: u16 = 0x0015; +pub const TBL_ADDR: u16 = 0x0016; +pub const TBL_A_LO: u16 = 0x0017; +pub const TBL_A_HI: u16 = 0x0018; +pub const TBL_B_LO: u16 = 0x0019; +pub const TBL_B_HI: u16 = 0x001A; // writing it commits the pair and steps the address +pub const DRAW_LO: u16 = 0x001B; // the draw the next cell would take (53 bits) +pub const DRAW_HI: u16 = 0x001C; +pub const UPTIME: u16 = 0x0020; +pub const CYCLES: u16 = 0x0022; +pub const UPDATES: u16 = 0x0024; +pub const JOBS: u16 = 0x0026; +pub const STEPS: u16 = 0x0028; +pub const BOUNDARY: u16 = 0x0100; +pub const STATE: u16 = 0x0110; +pub const FIELDS: u16 = 0x0200; // one per cell, 20 bits signed +pub const W: u16 = 0x1000; + +pub const CTL_START: u32 = 1; +pub const CTL_CONT: u32 = 2; +pub const CTL_STOP: u32 = 4; +pub const CTL_LOADSTATE: u32 = 8; +pub const CTL_CLEARW: u32 = 16; +pub const CTL_CLRCNT: u32 = 32; + +pub const MODE_T0: u32 = 0; +pub const MODE_GIBBS: u32 = 1; +pub const MODE_JACOBI: u32 = 2; +pub const MODE_REPEAT: u32 = 1 << 8; +pub const MODE_FINAL_T0: u32 = 1 << 9; + +pub const CELLS: usize = 64; +pub const LEVELS: usize = 64; +pub const TABLE: usize = 8192; +pub const MAX_WBITS: u32 = 13; +/// The draw pipeline's depth: a job that samples spends this many clocks priming it. +pub const PRIME: u64 = 9; +/// The largest field a cell's register holds, either sign. +pub const FIELD_MAX: i64 = (1 << 19) - 1; +/// The largest cost the register holds. +pub const COST_MAX: i64 = (1 << 20) - 1; +/// SplitMix64's increment. +pub const GOLDEN: u64 = 0x9E37_79B9_7F4A_7C15; +/// The edge of the 53-bit scale: a cut point of this size means "always below". +pub const TWO53: u64 = 1 << 53; + +pub const TERM_NONE: u32 = 0; +pub const TERM_FP: u32 = 1; +pub const TERM_2C: u32 = 2; +pub const TERM_BOUND: u32 = 3; diff --git a/wai/wai-quantum-fpga/src/script.rs b/wai/wai-quantum-fpga/src/script.rs new file mode 100644 index 000000000..9d90dd4da --- /dev/null +++ b/wai/wai-quantum-fpga/src/script.rs @@ -0,0 +1,50 @@ +//! Records a session against the model as a script the RTL testbench replays: every write, every read +//! with the value the model returned (the RTL must return the same), and every wait. +//! +//! W aaaa dddddddd write +//! R aaaa dddddddd mmmmmmmm read, expect (value & mask) == (d & mask) +//! I wait until not busy + +use crate::model::ModelPort; +use crate::port::{Error, Port}; +use crate::regs; +use std::fmt::Write as _; +use std::time::Duration; + +pub struct ScriptPort { + pub model: ModelPort, + pub text: String, + pub reads: usize, +} + +impl ScriptPort { + pub fn new() -> Self { + ScriptPort { model: ModelPort::new(), text: String::new(), reads: 0 } + } +} + +impl Default for ScriptPort { + fn default() -> Self { + Self::new() + } +} + +impl Port for ScriptPort { + fn write(&mut self, addr: u16, val: u32) -> Result<(), Error> { + let _ = writeln!(self.text, "W {addr:04x} {val:08x}"); + self.model.write(addr & 0x1FFF, val) + } + fn read(&mut self, addr: u16) -> Result { + let v = self.model.read(addr & 0x1FFF)?; + let a = addr & 0x1FFF; + // uptime counts real clocks, which the model cannot know + let mask = if a == regs::UPTIME || a == regs::UPTIME + 1 { 0u32 } else { 0xFFFF_FFFFu32 }; + let _ = writeln!(self.text, "R {addr:04x} {v:08x} {mask:08x}"); + self.reads += 1; + Ok(v) + } + fn wait_idle(&mut self, t: Duration) -> Result<(), Error> { + self.text.push_str("I\n"); + self.model.wait_idle(t) + } +} diff --git a/wai/wai-quantum-fpga/tb/tb_landscape.v b/wai/wai-quantum-fpga/tb/tb_landscape.v new file mode 100644 index 000000000..33cdd8911 --- /dev/null +++ b/wai/wai-quantum-fpga/tb/tb_landscape.v @@ -0,0 +1,80 @@ +// Replays a session recorded from the host's software model (`vqpu-edge gen-tb`) against the RTL, +// through the EMIO port and its synchroniser, with the PS side deliberately off the PL clock. +// Every read must return what the model returned. A mismatch names the script line. +`timescale 1ns/1ps +module tb; + reg clk = 0; + always #5 clk = ~clk; // pl_clk0, 100 MHz + reg [94:0] emio_o = 95'd0; + wire [94:0] emio_i; +`ifdef MULTI + landscape_multi #(.K(4)) dut (.clk(clk), .emio_o(emio_o), .emio_i(emio_i)); +`else + landscape_port dut (.clk(clk), .emio_o(emio_o), .emio_i(emio_i)); +`endif + + integer fd, r, errors, reads, line, t; + reg [31:0] op, a, d, m, got; + reg req; + + task txn(input we, input [15:0] addr, input [31:0] wdata, output [31:0] rdata); + reg ack0; + begin + ack0 = emio_i[82]; + emio_o[31:0] = wdata; + #37; // a PS bus write, some time before the next + req = ~req; + emio_o[81] = we; + emio_o[79:64] = addr; + emio_o[80] = req; + t = 0; + while (emio_i[82] == ack0) begin + #13; + t = t + 1; + if (t > 20000) begin + $display("TIMEOUT waiting for ack at script line %0d", line); + $finish; + end + end + rdata = emio_i[63:32]; + #29; + end + endtask + + initial begin + errors = 0; reads = 0; line = 0; req = 0; + #3000; // past the port's power-on hold-off + fd = $fopen("landscape_tb.script", "r"); + if (fd == 0) begin $display("no script"); $finish; end + r = $fscanf(fd, "%s", op); + while (r == 1) begin + line = line + 1; + if (op == "W") begin + r = $fscanf(fd, " %h %h", a, d); + txn(1'b1, a[15:0], d, got); + end else if (op == "R") begin + r = $fscanf(fd, " %h %h %h", a, d, m); + txn(1'b0, a[15:0], 32'd0, got); + reads = reads + 1; + if ((got & m) !== (d & m)) begin + errors = errors + 1; + if (errors <= 25) $display("MISMATCH line %0d: R %h got %h want %h", line, a[15:0], got, d); + end + end else if (op == "I") begin + t = 0; + while (emio_i[83]) begin + #50; + t = t + 1; + if (t > 2000000) begin $display("TIMEOUT busy at line %0d", line); $finish; end + end + #20; + end else begin + $display("bad op at line %0d", line); + $finish; + end + r = $fscanf(fd, "%s", op); + end + $display("TB DONE: %0d lines, %0d checked reads, %0d mismatches, sim time %0t", line, reads, errors, $time); + $finish; + end +endmodule -- 2.54.0 (Apple Git-157) From c1137390e29f527178ded3c1e4fd3fcfbe6d07c8 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Fri, 6 Nov 2026 03:05:19 -0500 Subject: [PATCH 02/11] wai-quantum-landscape-fpga: named for what it is, with its README, provenance, workflow and first measurements The crate is wai-quantum-landscape-fpga, beside the fabric's simulated FPGA member, which has its own workflow. The README gives the board and part as data, what the design does and refuses, what it takes to build, the build and load steps, the checks and the power protocol. No bitstream is distributed: the vendor tool's licence limits a bitstream holding its components to that vendor's devices, which Apache-2.0 does not carry, so the README gives the build and the sha256 of the bitstream measured. measurements/2026-11-05 holds the first version's raw power logs and the predictions committed before them, unchanged. The workflow runs the unit tests and every check against the model of the logic on each change. Co-Authored-By: Claude Opus 5.5 --- .../workflows/wai-quantum-landscape-fpga.yml | 67 ++ .../2026-11-06-landscape-fpga-member.md | 6 + .../Cargo.lock | 2 +- .../Cargo.toml | 2 +- wai/wai-quantum-landscape-fpga/PROVENANCE.md | 30 + wai/wai-quantum-landscape-fpga/README.md | 199 +++++ .../landscape.tcl | 1 + .../measurements/2026-11-05/PREDICTION.txt | 54 ++ .../2026-11-05/PREDICTION_4core.txt | 23 + .../measurements/2026-11-05/README.md | 16 + .../2026-11-05/gibbs-recall-4core.dat | 754 ++++++++++++++++++ .../measurements/2026-11-05/gibbs-recall.dat | 753 +++++++++++++++++ .../measurements/2026-11-05/k1_rdet.dat | 723 +++++++++++++++++ .../measurements/2026-11-05/null.dat | 505 ++++++++++++ .../measurements/2026-11-05/solve-assign.dat | 505 ++++++++++++ .../measurements/2026-11-05/step.dat | 610 ++++++++++++++ .../measurements/2026-11-05/t0-idle.dat | 505 ++++++++++++ .../2026-11-05/t0-recall-4core.dat | 754 ++++++++++++++++++ .../2026-11-05/t0-recall-50MHz.dat | 505 ++++++++++++ .../measurements/2026-11-05/t0-recall.dat | 753 +++++++++++++++++ .../measurements/2026-11-06/PREDICTION.txt | 0 .../rtl/landscape.xdc | 1 + .../rtl/landscape_core.v | 1 + .../rtl/landscape_multi.v | 1 + .../rtl/landscape_port.v | 1 + .../rtl/landscape_top.v | 1 + .../rtl/landscape_top_multi.v | 1 + .../scripts/build_bitstream.sh | 20 + .../scripts/build_host.sh | 10 + .../scripts/power_campaign.sh | 19 +- .../scripts/power_tables.py | 1 + .../scripts/run_all.sh | 17 +- .../scripts/step_settle.py | 1 + .../src/board.rs | 10 +- .../src/conform.rs | 0 .../src/emio.rs | 0 .../src/engine.rs | 0 .../src/lib.rs | 0 .../src/main.rs | 58 +- .../src/member.rs | 0 .../src/model.rs | 0 .../src/port.rs | 0 .../src/power.rs | 2 +- .../src/regs.rs | 0 .../src/script.rs | 0 .../tb/tb_landscape.v | 1 + 46 files changed, 6858 insertions(+), 54 deletions(-) create mode 100644 .github/workflows/wai-quantum-landscape-fpga.yml create mode 100644 wai/changes/2026-11-06-landscape-fpga-member.md rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/Cargo.lock (99%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/Cargo.toml (96%) create mode 100644 wai/wai-quantum-landscape-fpga/PROVENANCE.md create mode 100644 wai/wai-quantum-landscape-fpga/README.md rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/landscape.tcl (98%) create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-05/PREDICTION.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-05/PREDICTION_4core.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-05/README.md create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-05/gibbs-recall-4core.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-05/gibbs-recall.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-05/k1_rdet.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-05/null.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-05/solve-assign.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-05/step.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-05/t0-idle.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-05/t0-recall-4core.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-05/t0-recall-50MHz.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-05/t0-recall.dat rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/measurements/2026-11-06/PREDICTION.txt (100%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/rtl/landscape.xdc (91%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/rtl/landscape_core.v (99%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/rtl/landscape_multi.v (97%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/rtl/landscape_port.v (98%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/rtl/landscape_top.v (90%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/rtl/landscape_top_multi.v (88%) create mode 100755 wai/wai-quantum-landscape-fpga/scripts/build_bitstream.sh create mode 100755 wai/wai-quantum-landscape-fpga/scripts/build_host.sh rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/scripts/power_campaign.sh (62%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/scripts/power_tables.py (97%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/scripts/run_all.sh (57%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/scripts/step_settle.py (96%) mode change 100755 => 100644 rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/src/board.rs (93%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/src/conform.rs (100%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/src/emio.rs (100%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/src/engine.rs (100%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/src/lib.rs (100%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/src/main.rs (84%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/src/member.rs (100%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/src/model.rs (100%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/src/port.rs (100%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/src/power.rs (98%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/src/regs.rs (100%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/src/script.rs (100%) rename wai/{wai-quantum-fpga => wai-quantum-landscape-fpga}/tb/tb_landscape.v (98%) diff --git a/.github/workflows/wai-quantum-landscape-fpga.yml b/.github/workflows/wai-quantum-landscape-fpga.yml new file mode 100644 index 000000000..d5e3a6988 --- /dev/null +++ b/.github/workflows/wai-quantum-landscape-fpga.yml @@ -0,0 +1,67 @@ +name: wai-quantum-landscape-fpga reference design + +# The FPGA board's member of energy-landscape jobs (wai/wai-quantum-landscape-fpga): its unit tests, and every +# check of the conformance set and every worked example run against the register-level model of the +# logic (src/model.rs), which the logic's testbench is held to. No board and no vendor tool here: the +# board's own runs are in the crate's measurements and README. + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: [main] + paths: + - 'wai/wai-quantum-landscape-fpga/**' + - 'wai/wai-quantum/src/quantum_landscape/**' + - 'wai/wai-quantum/src/quantum_landscape.rs' + - 'wai/wai-quantum/src/repro.rs' + - 'wai/quantum-conformance/landscape/**' + - 'rust-toolchain.toml' + - '.github/workflows/wai-quantum-landscape-fpga.yml' + pull_request: + paths: + - 'wai/wai-quantum-landscape-fpga/**' + - 'wai/wai-quantum/src/quantum_landscape/**' + - 'wai/wai-quantum/src/quantum_landscape.rs' + - 'wai/wai-quantum/src/repro.rs' + - 'wai/quantum-conformance/landscape/**' + - 'rust-toolchain.toml' + - '.github/workflows/wai-quantum-landscape-fpga.yml' + +jobs: + model: + name: the member against the model of the logic · linux, release + runs-on: linux + timeout-minutes: 60 + steps: + - uses: actions/checkout@v5 + - name: Install Rust 1.99.1 + shell: bash + run: | + export PATH="$HOME/.cargo/bin:$PATH" + command -v rustup >/dev/null 2>&1 || \ + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.99.1 + rustup toolchain install 1.99.1 --profile minimal + rustup default 1.99.1 + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + - name: Unit tests + shell: bash + working-directory: wai/wai-quantum-landscape-fpga + run: cargo test --locked --release + - name: The conformance set and the worked examples, on the model + shell: bash + working-directory: wai/wai-quantum-landscape-fpga + run: | + set -euo pipefail + cargo run --locked --release -- selftest --scale 0.05 --seeds 5 | tee "$RUNNER_TEMP/selftest.log" + grep -q 'sample kernel: the chain vectors, bit for bit' "$RUNNER_TEMP/selftest.log" + grep -qE '^[0-9]+ checks, 0 failed$' "$RUNNER_TEMP/selftest.log" + - name: The testbench's script records from the model + shell: bash + working-directory: wai/wai-quantum-landscape-fpga + run: | + set -euo pipefail + cargo run --locked --release -- gen-tb "$RUNNER_TEMP/landscape_tb.script" + test "$(grep -c '^R ' "$RUNNER_TEMP/landscape_tb.script")" -gt 200 diff --git a/wai/changes/2026-11-06-landscape-fpga-member.md b/wai/changes/2026-11-06-landscape-fpga-member.md new file mode 100644 index 000000000..1927e3165 --- /dev/null +++ b/wai/changes/2026-11-06-landscape-fpga-member.md @@ -0,0 +1,6 @@ +### wai-quantum-landscape-fpga: an FPGA board as a member of energy-landscape jobs + +- **`wai-quantum-landscape-fpga`** is a reference design: the settle and sample kernels of `wai.quantum.landscape/1` in programmable logic for up to 64 cells (`rtl/`), the register-level model the logic is tested against, a testbench that replays the model's session against the logic, and the host that runs jobs on the board as a `Member`, holds it to the conformance set and meters its power by run minus halt. The board, its part and its sensor are named as data in its README. +- **The sample kernel takes the reference's draws**: the logic counts and mixes them as SplitMix64 does and compares 53 bits against cut points the host computes with the reference's `cuts`, so it needs no exponential. On the board both kernels are bit-exact against the `settle` and `chains` vectors, every worked example over 200 seeds answers as the CPU reference does, and the member earns its parity by running those vectors when it opens. +- **The measurements** are board-level, one board and one meter, each campaign with its prediction committed first (`measurements/`). No bitstream is distributed: the vendor tool's licence limits a bitstream to that vendor's devices, so the README gives the build steps. +- The design came from a private repository and is released under `Apache-2.0` by its copyright holder (`wai-quantum-landscape-fpga/PROVENANCE.md`). diff --git a/wai/wai-quantum-fpga/Cargo.lock b/wai/wai-quantum-landscape-fpga/Cargo.lock similarity index 99% rename from wai/wai-quantum-fpga/Cargo.lock rename to wai/wai-quantum-landscape-fpga/Cargo.lock index 02f66be38..6e413a3b6 100644 --- a/wai/wai-quantum-fpga/Cargo.lock +++ b/wai/wai-quantum-landscape-fpga/Cargo.lock @@ -432,7 +432,7 @@ dependencies = [ ] [[package]] -name = "wai-quantum-fpga" +name = "wai-quantum-landscape-fpga" version = "0.1.0" dependencies = [ "blake3", diff --git a/wai/wai-quantum-fpga/Cargo.toml b/wai/wai-quantum-landscape-fpga/Cargo.toml similarity index 96% rename from wai/wai-quantum-fpga/Cargo.toml rename to wai/wai-quantum-landscape-fpga/Cargo.toml index e598ee580..bb5999a46 100644 --- a/wai/wai-quantum-fpga/Cargo.toml +++ b/wai/wai-quantum-landscape-fpga/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "wai-quantum-fpga" +name = "wai-quantum-landscape-fpga" version = "0.1.0" edition = "2024" rust-version = "1.85" diff --git a/wai/wai-quantum-landscape-fpga/PROVENANCE.md b/wai/wai-quantum-landscape-fpga/PROVENANCE.md new file mode 100644 index 000000000..b567336b9 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/PROVENANCE.md @@ -0,0 +1,30 @@ +# Where this design came from + +The engine's logic, its register-level model, the host and the power-measurement scripts were +written in a private repository, where the first version of the design was built and measured on +the board, at commit `24bb5c8e8032e95a614a9db1e6f2e98ac7ea1233`. They are released here under this +repository's `Apache-2.0`, by the copyright holder's decision of 2026-11-05. This repository's +`LICENSE` governs everything under this directory; the files carry +`SPDX-License-Identifier: Apache-2.0`. + +## What changed from that code + +- **The draws.** The first version drew its own randomness from a 128-bit xoroshiro128+ register and + compared 16 bits against 16-bit cut points, so its sampling was statistical: right in distribution, + different in every bit. Here the engine computes the reference's draws, SplitMix64 over a counter, and + compares 53 bits against 54-bit cut points the host computes with the reference's `cuts`, so its + sampling kernel gives the reference's bits. +- **The table window.** The first version held fields −64 to 63 and refused a job whose uncertain + fields fell outside. Here the window's start and width are registers, and the host places them. +- **Fields and cost** widen from 11 and 10 bits to 20, and a cell update takes four clocks, not three. +- **The job form.** The first host read a request of `key=value` lines and printed a replay line of its + own. Here the host is a `Member` of `wai_quantum::quantum_landscape`, so a job is the spec's JSON + request, its answer the spec's answer, sealed, and the starts, default temperature, limits and + refusals are the reference's, because they are the reference's code. +- **Names.** Modules, files and the engine's ID (`LAND`) are named for what they do. + +## What is not here + +No bitstream (README, "Build and load"). The first version's raw power logs are in +`measurements/2026-11-05/` as they were written, under that version's tool name; its predictions are +the files committed before that measurement, copied unchanged. diff --git a/wai/wai-quantum-landscape-fpga/README.md b/wai/wai-quantum-landscape-fpga/README.md new file mode 100644 index 000000000..5cb9e151b --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/README.md @@ -0,0 +1,199 @@ +# 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`), whole board, 10 mW steps | +| clock | the processing system's `pl_clk0`, 100 MHz | +| job port | 95 EMIO lines of the processing system's GPIO controller, through `/dev/mem` | + +The register map is `src/regs.rs`. Nothing else is board-specific except `landscape.tcl` (part and +board part), `src/emio.rs` (the GPIO controller's addresses) 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 continues the chain, the counter running on. + +**The member.** `src/member.rs` implements `wai_quantum::quantum_landscape::member::Member`. It is +named `fpga:kv260-landscape-64`. It does not assume its parity: on opening, it runs the conformance +set's `settle` and `chains` vectors on the device and states `bit-exact` for a kernel only if every +vector comes back bit for bit. The reader, the truth, the energy, the verdict, 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. + +**The job port is EMIO, not AXI.** A processor load from an AXI address whose logic is gone never +completes and hangs the board until it is power-cycled; on a shared board another session's reload +can do that between two accesses. An EMIO line is a processor register: with the design gone a read +returns junk, the ID check fails, and the job gets an error. The cost is about 1.6 µs a register +access. + +**Limits, and what the host refuses with a sentence:** more than 64 variables; a field beyond +±524,287 or a cost beyond 1,048,575; a job whose uncertain fields span more than 8,192. + +## 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, and we added swap while it built | +| a Rust toolchain | the repository's pinned version (`rust-toolchain.toml`), with the `aarch64-unknown-linux-gnu` target and `aarch64-linux-gnu-gcc` | + +The design uses no licensed IP: its one vendor block is the processing system's configuration +(`zynq_ultra_ps_e` 3.5), and 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 +sh wai-quantum-landscape-fpga/scripts/build_bitstream.sh # -> 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 +``` + +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 bitstream measured below had sha256 +`feea5015b2f8d85049fd48bcc66493a411d551326365f67b1db2ae65ec00074c`; 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, random settles, field read-back +./wai-quantum-landscape-fpga agree --seeds 200 # every worked example, board against the CPU reference +./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 -- gen-tb s.txt # the testbench's script, recorded from the model +``` + +The testbench (`tb/tb_landscape.v`, plain Verilog) replays that script through the EMIO port and +its synchroniser and compares every read with the model's. We ran it in the vendor's simulator +(xsim 2026.1): 11,963 lines, 285 checked reads, 0 mismatches, and a flipped expectation is reported. +An open-source simulator was not tried. + +`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, a null (halt against halt), the same + job at half the clock (dynamic power must halve), and a known-answer vehicle where one exists. +- **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 +(about 3.1 W idle), so these are differences of a few percent of the total. + +MEASURED_V2_PLACEHOLDER + +**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. +- **The four-core build** of the first version fit (97,932 LUTs, 98% of slices, timing met); this + version's core is larger (below) and four of it were not built. The host addresses one core at a + time or all at once, and does not schedule different chains on different cores. +- **The job port is slow.** At 1.6 µs a register access, a full table level is 128 × 4 writes; a job + with long schedules on wide windows spends most of its time writing the table. +- **A cell update takes four clocks**, and in sequential mode one of 64 cones is used at a time. + +## Files + +| | | +|---|---| +| `rtl/landscape_core.v` | the engine: cones, the T0, GIBBS and JACOBI modes, the draw pipeline, the table, counters | +| `rtl/landscape_port.v`, `rtl/landscape_top.v` | the EMIO port and the top level | +| `rtl/landscape_multi.v`, `rtl/landscape_top_multi.v` | several engines behind one port | +| `landscape.tcl`, `rtl/landscape.xdc` | the build, and the constraints on the port's crossing | +| `src/` | the host: register map, ports, driver, member, model, checks, power meter, command line | +| `tb/tb_landscape.v` | the testbench | +| `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). diff --git a/wai/wai-quantum-fpga/landscape.tcl b/wai/wai-quantum-landscape-fpga/landscape.tcl similarity index 98% rename from wai/wai-quantum-fpga/landscape.tcl rename to wai/wai-quantum-landscape-fpga/landscape.tcl index 3c4cece14..953395ef4 100644 --- a/wai/wai-quantum-fpga/landscape.tcl +++ b/wai/wai-quantum-landscape-fpga/landscape.tcl @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 # Bitstream build for the energy-landscape engine. Vivado batch mode: # vivado -mode batch -source landscape.tcl -tclargs [FREQ ] [TAG ] [JOBS ] [CORES ] # The part and board are data: PART and BOARD below. The processing system provides the clock and the diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/PREDICTION.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/PREDICTION.txt new file mode 100644 index 000000000..753f5782c --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/PREDICTION.txt @@ -0,0 +1,54 @@ +PRE-REGISTERED, 2026-11-05, before the vQPU-EDGE bitstream had been loaded on the board, and before +any power was read from it. Written from the RTL, the software model's activity counts +(`vqpu-edge activity`) and the board's own earlier measurements. Not edited after the data. + +WHAT IS MEASURED +Run minus halt on ONE bitstream (kv260/edge, 100 MHz pl_clk0), whole-board INA260, by the board's +written rules: no reload between states, randomised order each pass, sampling on the board in the +measuring process with nothing else running, reload detection per point (engine uptime counter must +keep counting; dmesg FPGA-write count must not move; ID must read back), pass dropped if flagged, +statistic = mean of per-pass differences of medians, error = SE across passes. Label of every +number: board-level, this KV260, this design. It is NOT the 0.1596 pJ/accumulate of the old +constant-folded vehicle and must not be quoted as that: this lowering keeps W in flops and adds a +product LUT, a compressor and a runtime-theta comparator to every cone, so it has its own rate. + +THE INSTRUMENT CHECKS, which gate everything else + K1 known answer: the September vehicle ev_rdet.bit.bin, run/halt by its own script kv_runhalt.sh, + must reproduce 61.7 mW (its 2026-09-28 run/halt value, +/- 6.5). PASS if within 2 combined sigma. + If it fails, no number below is reported as measured. + K2 null: on the vQPU-EDGE bitstream, halt against halt (one slot labelled run): 0 mW. + PASS if |delta| < 2 SE. + +PREDICTIONS (point value, and the range outside which I am wrong) +Reasoning: the old det vehicle drew 61.7 mW at 8.68 state flips per clock, i.e. ~71 pJ per flip with +its constant-folded cones. This lowering has ~3x the LUTs per cone (product + compressor + tree + +comparators), and a flip drives all 64 cones, so I take 2.5x per flip, uncertain by 2x either way. + + class what repeats flips/clock predicted P_dyn range + t0-recall dense Hebbian n=64 (8 patterns), T=0 settle 6.88 122 mW 60..250 + from a 30%-corrupted cue, 5 steps, reload + gibbs-recall same landscape, 8 sequential Gibbs sweeps at 0.028 5 mW 0..20 + beta=4 + one T=0 step, reload (1539 clocks/job) + - cones barely move; what is left is PRNG, table + read, field mux and counters + solve-assign the 3x3 equality landscape (n=9), 39-sweep anneal ~0.1 3 mW 0..15 + from T=1, final T=0 step; 55 of the 64 cones have + W=0 and cannot switch + t0-idle W=0, bound 0: the job FSM looping with a static 0 1 mW 0..10 + datapath (control and counters only) + +Derived, for t0-recall: 12.5 M settles/s at 8 clocks each => ~10 nJ per settle; with its accumulate +count (5 steps x nnz) that is about 0.5 pJ per accumulate, range 0.25..1.0. I expect it to be WORSE +than the old 0.16 pJ/accumulate, because the couplings are now data, not wiring. + +WHAT WOULD REFUTE THE REASONING + - t0-recall outside 60..250 mW: the per-flip cost of this lowering is not 1-5x the old one. + - gibbs-recall above 20 mW: something other than state flips (the field mux, the table, the PRNG) + costs far more than the toggle study's numbers say. + - t0-idle above 10 mW: control is not negligible, and every per-job figure carries it. + +WHAT THIS RUN CANNOT RESOLVE (said now, so a null is not read as a finding) + gibbs-recall, solve-assign and t0-idle are predicted at or below the protocol's floor (paired SE + ~2-7 mW at 8-12 passes). A result consistent with 0 there means "below what one core on this + meter resolves", not "free". A second meter on the PL rail (or K replicated cores) is what would + resolve them. diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/PREDICTION_4core.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/PREDICTION_4core.txt new file mode 100644 index 000000000..9f2d588f8 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/PREDICTION_4core.txt @@ -0,0 +1,23 @@ +PRE-REGISTERED, 2026-11-05, written while the single-core campaign was still running on the board +and before any of its numbers had been read, and before the four-core bitstream was ever loaded. + +WHY. The single-core prediction put gibbs-recall at ~5 mW, at or below the paired protocol's +floor. The notes' own lesson (FEASIBILITY_parity_2026-10-04.txt): amplify the effect, don't average +the noise. rtl/edge_multi.v puts four engines behind one port; `vqpu-edge meter ... --cores 4` +runs the same job on all four at once (BroadcastPort), so the effect is ~4x while the sensor quantum +and drift stay put. + +WHAT IS MEASURED. The same run/halt protocol as the single-core campaign, on the four-core +bitstream, 100 MHz, classes t0-recall and gibbs-recall. + +PREDICTIONS + 1. Scaling: P_dyn(4 cores) / P_dyn(1 core, this day's campaign) = 4.0, PASS range 3.4 .. 4.6 for + t0-recall. Outside it, cores do not add independently (shared clock tree, placement density, + rail), and the per-core numbers from the four-core build must not be divided by four. + 2. t0-recall, four cores: 4 x 122 = 488 mW (range 240 .. 1000), i.e. four times the single-core + prediction, whatever the single-core measurement turns out to be. + 3. gibbs-recall, four cores: 4 x 5 = 20 mW (range 0 .. 80). If the single-core run could not + resolve it, this is the number that can: at 20 mW and SE ~6 mW it is ~3 sigma. + +LABEL. Board-level, this KV260, this (four-core) design. Per-core figures from it are the four-core +value divided by four only if prediction 1 passes. diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/README.md b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/README.md new file mode 100644 index 000000000..35a602c14 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/README.md @@ -0,0 +1,16 @@ +# 2026-11-05: the first version of the design + +Raw logs from the first version (draws of its own, 16-bit cut points, fields of 11 bits), as the +meter wrote them, under that version's tool name. `PREDICTION.txt` and `PREDICTION_4core.txt` are the +predictions committed before each measurement, copied unchanged. + +| file | what | +|---|---| +| `step.dat` | the run/halt step response, 300 s each way | +| `k1_rdet.dat` | the known-answer vehicle (an earlier design measured in September at 61.7 ± 6.5 mW), by its own script | +| `null.dat` | halt against halt | +| `t0-recall.dat`, `t0-recall-50MHz.dat` | the settle job at 100 and 50 MHz | +| `gibbs-recall.dat`, `t0-idle.dat`, `solve-assign.dat` | the sampling job, the idle loop, a small annealing job | +| `t0-recall-4core.dat`, `gibbs-recall-4core.dat` | the same jobs on four cores at once | + +Summarise any of them with `wai-quantum-fpga analyze ` or `python3 scripts/power_tables.py `. diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/gibbs-recall-4core.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/gibbs-recall-4core.dat new file mode 100644 index 000000000..fab79f478 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/gibbs-recall-4core.dat @@ -0,0 +1,754 @@ +# class gibbs-recall cores 4 +# vqpu-edge meter passes=12 settle=60s samples=30 null=false sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793922349, tv_nsec: 240818196 } +# pass state sample power_uW +1 run 1 3290000 +1 run 2 3540000 +1 run 3 3290000 +1 run 4 3310000 +1 run 5 3300000 +1 run 6 3280000 +1 run 7 3700000 +1 run 8 3290000 +1 run 9 3280000 +1 run 10 3290000 +1 run 11 3280000 +1 run 12 3280000 +1 run 13 3300000 +1 run 14 3280000 +1 run 15 3280000 +1 run 16 3280000 +1 run 17 3280000 +1 run 18 3290000 +1 run 19 3280000 +1 run 20 3270000 +1 run 21 3280000 +1 run 22 3290000 +1 run 23 3290000 +1 run 24 3290000 +1 run 25 3280000 +1 run 26 3280000 +1 run 27 3290000 +1 run 28 3290000 +1 run 29 3280000 +1 run 30 3290000 +# point pass=1 state=run jobs_per_s=64976.0 updates_per_s=33267709.4 clock_hz=99998038 +1 halt 1 3260000 +1 halt 2 3240000 +1 halt 3 3240000 +1 halt 4 3250000 +1 halt 5 3240000 +1 halt 6 3240000 +1 halt 7 3250000 +1 halt 8 3250000 +1 halt 9 3240000 +1 halt 10 3240000 +1 halt 11 3240000 +1 halt 12 3240000 +1 halt 13 3240000 +1 halt 14 3240000 +1 halt 15 3240000 +1 halt 16 3250000 +1 halt 17 3240000 +1 halt 18 3240000 +1 halt 19 3240000 +1 halt 20 3240000 +1 halt 21 3250000 +1 halt 22 3240000 +1 halt 23 3240000 +1 halt 24 3260000 +1 halt 25 3260000 +1 halt 26 3240000 +1 halt 27 3240000 +1 halt 28 3260000 +1 halt 29 3240000 +1 halt 30 3240000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998032 +2 run 1 3290000 +2 run 2 3290000 +2 run 3 3290000 +2 run 4 3280000 +2 run 5 3280000 +2 run 6 3270000 +2 run 7 3270000 +2 run 8 3290000 +2 run 9 3280000 +2 run 10 3280000 +2 run 11 3280000 +2 run 12 3280000 +2 run 13 3300000 +2 run 14 3280000 +2 run 15 3280000 +2 run 16 3280000 +2 run 17 3290000 +2 run 18 3280000 +2 run 19 3280000 +2 run 20 3280000 +2 run 21 3290000 +2 run 22 3290000 +2 run 23 3280000 +2 run 24 3270000 +2 run 25 3280000 +2 run 26 3280000 +2 run 27 3280000 +2 run 28 3270000 +2 run 29 3270000 +2 run 30 3280000 +# point pass=2 state=run jobs_per_s=64976.0 updates_per_s=33267706.0 clock_hz=99998028 +2 halt 1 3250000 +2 halt 2 3240000 +2 halt 3 3240000 +2 halt 4 3240000 +2 halt 5 3240000 +2 halt 6 3240000 +2 halt 7 3240000 +2 halt 8 3240000 +2 halt 9 3260000 +2 halt 10 3260000 +2 halt 11 3240000 +2 halt 12 3240000 +2 halt 13 3260000 +2 halt 14 3240000 +2 halt 15 3250000 +2 halt 16 3250000 +2 halt 17 3240000 +2 halt 18 3250000 +2 halt 19 3240000 +2 halt 20 3240000 +2 halt 21 3240000 +2 halt 22 3240000 +2 halt 23 3240000 +2 halt 24 3240000 +2 halt 25 3240000 +2 halt 26 3250000 +2 halt 27 3240000 +2 halt 28 3240000 +2 halt 29 3240000 +2 halt 30 3240000 +# point pass=2 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998024 +3 run 1 3290000 +3 run 2 3280000 +3 run 3 3280000 +3 run 4 3280000 +3 run 5 3290000 +3 run 6 3300000 +3 run 7 3290000 +3 run 8 3290000 +3 run 9 3290000 +3 run 10 3290000 +3 run 11 3290000 +3 run 12 3280000 +3 run 13 3300000 +3 run 14 3290000 +3 run 15 3310000 +3 run 16 3290000 +3 run 17 3290000 +3 run 18 3290000 +3 run 19 3290000 +3 run 20 3290000 +3 run 21 3290000 +3 run 22 3290000 +3 run 23 3280000 +3 run 24 3280000 +3 run 25 3300000 +3 run 26 3290000 +3 run 27 3290000 +3 run 28 3290000 +3 run 29 3290000 +3 run 30 3300000 +# point pass=3 state=run jobs_per_s=64976.0 updates_per_s=33267703.0 clock_hz=99998019 +3 halt 1 3250000 +3 halt 2 3260000 +3 halt 3 3270000 +3 halt 4 3260000 +3 halt 5 3270000 +3 halt 6 3260000 +3 halt 7 3250000 +3 halt 8 3250000 +3 halt 9 3260000 +3 halt 10 3250000 +3 halt 11 3250000 +3 halt 12 3250000 +3 halt 13 3250000 +3 halt 14 3250000 +3 halt 15 3250000 +3 halt 16 3250000 +3 halt 17 3250000 +3 halt 18 3270000 +3 halt 19 3260000 +3 halt 20 3250000 +3 halt 21 3240000 +3 halt 22 3250000 +3 halt 23 3250000 +3 halt 24 3250000 +3 halt 25 3250000 +3 halt 26 3280000 +3 halt 27 3270000 +3 halt 28 3240000 +3 halt 29 3250000 +3 halt 30 3250000 +# point pass=3 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998016 +4 halt 1 3250000 +4 halt 2 3260000 +4 halt 3 3260000 +4 halt 4 3250000 +4 halt 5 3250000 +4 halt 6 3250000 +4 halt 7 3280000 +4 halt 8 3250000 +4 halt 9 3260000 +4 halt 10 3270000 +4 halt 11 3280000 +4 halt 12 3260000 +4 halt 13 3250000 +4 halt 14 3260000 +4 halt 15 3250000 +4 halt 16 3250000 +4 halt 17 3250000 +4 halt 18 3260000 +4 halt 19 3270000 +4 halt 20 3260000 +4 halt 21 3250000 +4 halt 22 3250000 +4 halt 23 3280000 +4 halt 24 3260000 +4 halt 25 3250000 +4 halt 26 3250000 +4 halt 27 3250000 +4 halt 28 3250000 +4 halt 29 3250000 +4 halt 30 3270000 +# point pass=4 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998014 +4 run 1 3290000 +4 run 2 3290000 +4 run 3 3280000 +4 run 4 3280000 +4 run 5 3300000 +4 run 6 3280000 +4 run 7 3290000 +4 run 8 3280000 +4 run 9 3290000 +4 run 10 3290000 +4 run 11 3290000 +4 run 12 3280000 +4 run 13 3280000 +4 run 14 3280000 +4 run 15 3290000 +4 run 16 3290000 +4 run 17 3290000 +4 run 18 3280000 +4 run 19 3290000 +4 run 20 3300000 +4 run 21 3280000 +4 run 22 3290000 +4 run 23 3280000 +4 run 24 3290000 +4 run 25 3280000 +4 run 26 3290000 +4 run 27 3280000 +4 run 28 3300000 +4 run 29 3290000 +4 run 30 3300000 +# point pass=4 state=run jobs_per_s=64976.0 updates_per_s=33267700.6 clock_hz=99998011 +5 halt 1 3250000 +5 halt 2 3250000 +5 halt 3 3250000 +5 halt 4 3240000 +5 halt 5 3240000 +5 halt 6 3250000 +5 halt 7 3250000 +5 halt 8 3260000 +5 halt 9 3250000 +5 halt 10 3240000 +5 halt 11 3240000 +5 halt 12 3250000 +5 halt 13 3240000 +5 halt 14 3250000 +5 halt 15 3250000 +5 halt 16 3260000 +5 halt 17 3260000 +5 halt 18 3250000 +5 halt 19 3250000 +5 halt 20 3240000 +5 halt 21 3240000 +5 halt 22 3240000 +5 halt 23 3250000 +5 halt 24 3240000 +5 halt 25 3260000 +5 halt 26 3240000 +5 halt 27 3250000 +5 halt 28 3250000 +5 halt 29 3250000 +5 halt 30 3240000 +# point pass=5 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998011 +5 run 1 3280000 +5 run 2 3300000 +5 run 3 3290000 +5 run 4 3290000 +5 run 5 3340000 +5 run 6 3320000 +5 run 7 3290000 +5 run 8 3290000 +5 run 9 3290000 +5 run 10 3290000 +5 run 11 3290000 +5 run 12 3290000 +5 run 13 3320000 +5 run 14 3300000 +5 run 15 3290000 +5 run 16 3290000 +5 run 17 3290000 +5 run 18 3280000 +5 run 19 3290000 +5 run 20 3300000 +5 run 21 3290000 +5 run 22 3290000 +5 run 23 3320000 +5 run 24 3290000 +5 run 25 3290000 +5 run 26 3290000 +5 run 27 3290000 +5 run 28 3300000 +5 run 29 3280000 +5 run 30 3290000 +# point pass=5 state=run jobs_per_s=64976.0 updates_per_s=33267699.6 clock_hz=99998008 +6 run 1 3300000 +6 run 2 3300000 +6 run 3 3290000 +6 run 4 3290000 +6 run 5 3280000 +6 run 6 3280000 +6 run 7 3280000 +6 run 8 3280000 +6 run 9 3280000 +6 run 10 3300000 +6 run 11 3280000 +6 run 12 3280000 +6 run 13 3280000 +6 run 14 3280000 +6 run 15 3280000 +6 run 16 3280000 +6 run 17 3280000 +6 run 18 3280000 +6 run 19 3290000 +6 run 20 3280000 +6 run 21 3280000 +6 run 22 3290000 +6 run 23 3280000 +6 run 24 3290000 +6 run 25 3310000 +6 run 26 3290000 +6 run 27 3290000 +6 run 28 3290000 +6 run 29 3280000 +6 run 30 3280000 +# point pass=6 state=run jobs_per_s=64976.0 updates_per_s=33267699.2 clock_hz=99998007 +6 halt 1 3230000 +6 halt 2 3240000 +6 halt 3 3240000 +6 halt 4 3250000 +6 halt 5 3240000 +6 halt 6 3240000 +6 halt 7 3250000 +6 halt 8 3240000 +6 halt 9 3240000 +6 halt 10 3250000 +6 halt 11 3250000 +6 halt 12 3250000 +6 halt 13 3240000 +6 halt 14 3240000 +6 halt 15 3340000 +6 halt 16 3250000 +6 halt 17 3240000 +6 halt 18 3240000 +6 halt 19 3250000 +6 halt 20 3580000 +6 halt 21 3250000 +6 halt 22 3260000 +6 halt 23 3240000 +6 halt 24 3250000 +6 halt 25 3240000 +6 halt 26 3240000 +6 halt 27 3240000 +6 halt 28 3250000 +6 halt 29 3240000 +6 halt 30 3250000 +# point pass=6 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998006 +7 halt 1 3250000 +7 halt 2 3240000 +7 halt 3 3240000 +7 halt 4 3250000 +7 halt 5 3240000 +7 halt 6 3240000 +7 halt 7 3240000 +7 halt 8 3240000 +7 halt 9 3240000 +7 halt 10 3250000 +7 halt 11 3240000 +7 halt 12 3250000 +7 halt 13 3690000 +7 halt 14 3240000 +7 halt 15 3240000 +7 halt 16 3250000 +7 halt 17 3250000 +7 halt 18 3260000 +7 halt 19 3260000 +7 halt 20 3240000 +7 halt 21 3240000 +7 halt 22 3250000 +7 halt 23 3240000 +7 halt 24 3240000 +7 halt 25 3240000 +7 halt 26 3240000 +7 halt 27 3240000 +7 halt 28 3240000 +7 halt 29 3260000 +7 halt 30 3240000 +# point pass=7 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997761 +7 run 1 3290000 +7 run 2 3280000 +7 run 3 3280000 +7 run 4 3290000 +7 run 5 3280000 +7 run 6 3280000 +7 run 7 3290000 +7 run 8 3280000 +7 run 9 3280000 +7 run 10 3290000 +7 run 11 3280000 +7 run 12 3280000 +7 run 13 3290000 +7 run 14 3290000 +7 run 15 3290000 +7 run 16 3290000 +7 run 17 3290000 +7 run 18 3300000 +7 run 19 3300000 +7 run 20 3290000 +7 run 21 3290000 +7 run 22 3280000 +7 run 23 3280000 +7 run 24 3290000 +7 run 25 3280000 +7 run 26 3280000 +7 run 27 3290000 +7 run 28 3280000 +7 run 29 3280000 +7 run 30 3290000 +# point pass=7 state=run jobs_per_s=64975.8 updates_per_s=33267628.3 clock_hz=99997794 +8 run 1 3280000 +8 run 2 3290000 +8 run 3 3280000 +8 run 4 3280000 +8 run 5 3280000 +8 run 6 3280000 +8 run 7 3300000 +8 run 8 3280000 +8 run 9 3290000 +8 run 10 3280000 +8 run 11 3320000 +8 run 12 3290000 +8 run 13 3290000 +8 run 14 3280000 +8 run 15 3310000 +8 run 16 3290000 +8 run 17 3280000 +8 run 18 3270000 +8 run 19 3290000 +8 run 20 3290000 +8 run 21 3270000 +8 run 22 3290000 +8 run 23 3280000 +8 run 24 3290000 +8 run 25 3280000 +8 run 26 3280000 +8 run 27 3290000 +8 run 28 3280000 +8 run 29 3290000 +8 run 30 3310000 +# point pass=8 state=run jobs_per_s=64975.8 updates_per_s=33267638.1 clock_hz=99997823 +8 halt 1 3250000 +8 halt 2 3240000 +8 halt 3 3260000 +8 halt 4 3260000 +8 halt 5 3240000 +8 halt 6 3240000 +8 halt 7 3240000 +8 halt 8 3250000 +8 halt 9 3240000 +8 halt 10 3240000 +8 halt 11 3250000 +8 halt 12 3260000 +8 halt 13 3240000 +8 halt 14 3250000 +8 halt 15 3230000 +8 halt 16 3240000 +8 halt 17 3250000 +8 halt 18 3250000 +8 halt 19 3250000 +8 halt 20 3250000 +8 halt 21 3240000 +8 halt 22 3260000 +8 halt 23 3250000 +8 halt 24 3240000 +8 halt 25 3250000 +8 halt 26 3240000 +8 halt 27 3260000 +8 halt 28 3250000 +8 halt 29 3250000 +8 halt 30 3250000 +# point pass=8 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997846 +9 halt 1 3250000 +9 halt 2 3240000 +9 halt 3 3240000 +9 halt 4 3250000 +9 halt 5 3240000 +9 halt 6 3250000 +9 halt 7 3250000 +9 halt 8 3240000 +9 halt 9 3240000 +9 halt 10 3240000 +9 halt 11 3250000 +9 halt 12 3240000 +9 halt 13 3240000 +9 halt 14 3250000 +9 halt 15 3250000 +9 halt 16 3240000 +9 halt 17 3240000 +9 halt 18 3240000 +9 halt 19 3240000 +9 halt 20 3240000 +9 halt 21 3240000 +9 halt 22 3240000 +9 halt 23 3250000 +9 halt 24 3260000 +9 halt 25 3240000 +9 halt 26 3240000 +9 halt 27 3240000 +9 halt 28 3240000 +9 halt 29 3240000 +9 halt 30 3240000 +# point pass=9 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997867 +9 run 1 3280000 +9 run 2 3290000 +9 run 3 3290000 +9 run 4 3290000 +9 run 5 3290000 +9 run 6 3300000 +9 run 7 3290000 +9 run 8 3290000 +9 run 9 3290000 +9 run 10 3290000 +9 run 11 3290000 +9 run 12 3310000 +9 run 13 3290000 +9 run 14 3290000 +9 run 15 3290000 +9 run 16 3290000 +9 run 17 3290000 +9 run 18 3280000 +9 run 19 3300000 +9 run 20 3310000 +9 run 21 3300000 +9 run 22 3290000 +9 run 23 3280000 +9 run 24 3290000 +9 run 25 3280000 +9 run 26 3290000 +9 run 27 3290000 +9 run 28 3290000 +9 run 29 3300000 +9 run 30 3290000 +# point pass=9 state=run jobs_per_s=64975.9 updates_per_s=33267658.0 clock_hz=99997883 +10 halt 1 3250000 +10 halt 2 3250000 +10 halt 3 3250000 +10 halt 4 3240000 +10 halt 5 3250000 +10 halt 6 3240000 +10 halt 7 3240000 +10 halt 8 3250000 +10 halt 9 3260000 +10 halt 10 3240000 +10 halt 11 3260000 +10 halt 12 3250000 +10 halt 13 3240000 +10 halt 14 3240000 +10 halt 15 3240000 +10 halt 16 3250000 +10 halt 17 3250000 +10 halt 18 3240000 +10 halt 19 3250000 +10 halt 20 3250000 +10 halt 21 3240000 +10 halt 22 3240000 +10 halt 23 3240000 +10 halt 24 3250000 +10 halt 25 3230000 +10 halt 26 3230000 +10 halt 27 3240000 +10 halt 28 3240000 +10 halt 29 3240000 +10 halt 30 3240000 +# point pass=10 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997898 +10 run 1 3280000 +10 run 2 3280000 +10 run 3 3290000 +10 run 4 3270000 +10 run 5 3280000 +10 run 6 3290000 +10 run 7 3290000 +10 run 8 3290000 +10 run 9 3290000 +10 run 10 3290000 +10 run 11 3290000 +10 run 12 3290000 +10 run 13 3290000 +10 run 14 3290000 +10 run 15 3290000 +10 run 16 3300000 +10 run 17 3290000 +10 run 18 3290000 +10 run 19 3290000 +10 run 20 3310000 +10 run 21 3290000 +10 run 22 3280000 +10 run 23 3290000 +10 run 24 3290000 +10 run 25 3290000 +10 run 26 3280000 +10 run 27 3300000 +10 run 28 3290000 +10 run 29 3290000 +10 run 30 3290000 +# point pass=10 state=run jobs_per_s=64975.9 updates_per_s=33267667.1 clock_hz=99997910 +11 run 1 3290000 +11 run 2 3290000 +11 run 3 3300000 +11 run 4 3280000 +11 run 5 3280000 +11 run 6 3280000 +11 run 7 3280000 +11 run 8 3290000 +11 run 9 3290000 +11 run 10 3280000 +11 run 11 3310000 +11 run 12 3280000 +11 run 13 3290000 +11 run 14 3280000 +11 run 15 3280000 +11 run 16 3290000 +11 run 17 3280000 +11 run 18 3280000 +11 run 19 3280000 +11 run 20 3280000 +11 run 21 3290000 +11 run 22 3280000 +11 run 23 3280000 +11 run 24 3280000 +11 run 25 3290000 +11 run 26 3290000 +11 run 27 3280000 +11 run 28 3280000 +11 run 29 3280000 +11 run 30 3290000 +# point pass=11 state=run jobs_per_s=64975.9 updates_per_s=33267670.1 clock_hz=99997920 +11 halt 1 3240000 +11 halt 2 3240000 +11 halt 3 3250000 +11 halt 4 3240000 +11 halt 5 3240000 +11 halt 6 3240000 +11 halt 7 3230000 +11 halt 8 3270000 +11 halt 9 3250000 +11 halt 10 3250000 +11 halt 11 3250000 +11 halt 12 3250000 +11 halt 13 3250000 +11 halt 14 3240000 +11 halt 15 3240000 +11 halt 16 3240000 +11 halt 17 3250000 +11 halt 18 3250000 +11 halt 19 3240000 +11 halt 20 3250000 +11 halt 21 3240000 +11 halt 22 3250000 +11 halt 23 3260000 +11 halt 24 3240000 +11 halt 25 3240000 +11 halt 26 3250000 +11 halt 27 3240000 +11 halt 28 3240000 +11 halt 29 3240000 +11 halt 30 3250000 +# point pass=11 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997928 +12 halt 1 3250000 +12 halt 2 3260000 +12 halt 3 3260000 +12 halt 4 3250000 +12 halt 5 3250000 +12 halt 6 3250000 +12 halt 7 3250000 +12 halt 8 3250000 +12 halt 9 3280000 +12 halt 10 3250000 +12 halt 11 3260000 +12 halt 12 3250000 +12 halt 13 3260000 +12 halt 14 3260000 +12 halt 15 3250000 +12 halt 16 3250000 +12 halt 17 3280000 +12 halt 18 3270000 +12 halt 19 3260000 +12 halt 20 3260000 +12 halt 21 3250000 +12 halt 22 3250000 +12 halt 23 3270000 +12 halt 24 3250000 +12 halt 25 3250000 +12 halt 26 3280000 +12 halt 27 3250000 +12 halt 28 3250000 +12 halt 29 3260000 +12 halt 30 3260000 +# point pass=12 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997935 +12 run 1 3280000 +12 run 2 3290000 +12 run 3 3280000 +12 run 4 3280000 +12 run 5 3280000 +12 run 6 3300000 +12 run 7 3280000 +12 run 8 3290000 +12 run 9 3280000 +12 run 10 3280000 +12 run 11 3290000 +12 run 12 3290000 +12 run 13 3280000 +12 run 14 3300000 +12 run 15 3280000 +12 run 16 3280000 +12 run 17 3290000 +12 run 18 3280000 +12 run 19 3280000 +12 run 20 3280000 +12 run 21 3290000 +12 run 22 3290000 +12 run 23 3280000 +12 run 24 3280000 +12 run 25 3280000 +12 run 26 3290000 +12 run 27 3290000 +12 run 28 3280000 +12 run 29 3300000 +12 run 30 3280000 +# point pass=12 state=run jobs_per_s=64975.9 updates_per_s=33267677.2 clock_hz=99997941 +# done SystemTime { tv_sec: 1793924510, tv_nsec: 651905562 } +# passes used 12 of 12 (dropped: []) +# run - halt = 42.1 +/- 1.9 mW (per-pass sd 6.6); 12/12 passes positive; 7 sample excursions beyond 6 MAD +# per-pass differences (mW): 50.0 40.0 40.0 40.0 40.0 40.0 50.0 35.0 50.0 50.0 40.0 30.0 +# board power while running 3286 mW; jobs/s 64975.9; updates/s 33267679.7; measured PL clock 99.998 MHz +# pre-registered prediction 20.0 mW: measured/predicted = 2.10, 11.7 sigma from it +# jobs/s and updates/s above are core 0's; all 4 cores run the same job diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/gibbs-recall.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/gibbs-recall.dat new file mode 100644 index 000000000..71ef4dbc3 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/gibbs-recall.dat @@ -0,0 +1,753 @@ +# class gibbs-recall +# vqpu-edge meter passes=12 settle=60s samples=30 null=false sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793913111, tv_nsec: 946190018 } +# pass state sample power_uW +1 run 1 3120000 +1 run 2 3110000 +1 run 3 3120000 +1 run 4 3100000 +1 run 5 3110000 +1 run 6 3120000 +1 run 7 3110000 +1 run 8 3110000 +1 run 9 3110000 +1 run 10 3110000 +1 run 11 3110000 +1 run 12 3120000 +1 run 13 3110000 +1 run 14 3110000 +1 run 15 3110000 +1 run 16 3120000 +1 run 17 3120000 +1 run 18 3140000 +1 run 19 3120000 +1 run 20 3110000 +1 run 21 3120000 +1 run 22 3130000 +1 run 23 3130000 +1 run 24 3120000 +1 run 25 3120000 +1 run 26 3140000 +1 run 27 3120000 +1 run 28 3110000 +1 run 29 3120000 +1 run 30 3130000 +# point pass=1 state=run jobs_per_s=64975.9 updates_per_s=33267684.8 clock_hz=99997963 +1 halt 1 3120000 +1 halt 2 3110000 +1 halt 3 3110000 +1 halt 4 3100000 +1 halt 5 3100000 +1 halt 6 3130000 +1 halt 7 3110000 +1 halt 8 3100000 +1 halt 9 3110000 +1 halt 10 3110000 +1 halt 11 3490000 +1 halt 12 3110000 +1 halt 13 3110000 +1 halt 14 3120000 +1 halt 15 3130000 +1 halt 16 3100000 +1 halt 17 3110000 +1 halt 18 3100000 +1 halt 19 3110000 +1 halt 20 3100000 +1 halt 21 3100000 +1 halt 22 3110000 +1 halt 23 3110000 +1 halt 24 3100000 +1 halt 25 3110000 +1 halt 26 3100000 +1 halt 27 3100000 +1 halt 28 3100000 +1 halt 29 3100000 +1 halt 30 3100000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997942 +2 run 1 3130000 +2 run 2 3110000 +2 run 3 3140000 +2 run 4 3130000 +2 run 5 3120000 +2 run 6 3120000 +2 run 7 3120000 +2 run 8 3120000 +2 run 9 3120000 +2 run 10 3120000 +2 run 11 3130000 +2 run 12 3140000 +2 run 13 3120000 +2 run 14 3120000 +2 run 15 3110000 +2 run 16 3120000 +2 run 17 3120000 +2 run 18 3110000 +2 run 19 3120000 +2 run 20 3130000 +2 run 21 3120000 +2 run 22 3110000 +2 run 23 3120000 +2 run 24 3120000 +2 run 25 3150000 +2 run 26 3120000 +2 run 27 3120000 +2 run 28 3120000 +2 run 29 3110000 +2 run 30 3110000 +# point pass=2 state=run jobs_per_s=64975.9 updates_per_s=33267678.8 clock_hz=99997946 +2 halt 1 3120000 +2 halt 2 3120000 +2 halt 3 3100000 +2 halt 4 3110000 +2 halt 5 3110000 +2 halt 6 3120000 +2 halt 7 3110000 +2 halt 8 3140000 +2 halt 9 3120000 +2 halt 10 3100000 +2 halt 11 3110000 +2 halt 12 3110000 +2 halt 13 3110000 +2 halt 14 3110000 +2 halt 15 3110000 +2 halt 16 3110000 +2 halt 17 3110000 +2 halt 18 3110000 +2 halt 19 3110000 +2 halt 20 3110000 +2 halt 21 3110000 +2 halt 22 3110000 +2 halt 23 3120000 +2 halt 24 3120000 +2 halt 25 3110000 +2 halt 26 3100000 +2 halt 27 3100000 +2 halt 28 3110000 +2 halt 29 3120000 +2 halt 30 3110000 +# point pass=2 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997947 +3 halt 1 3110000 +3 halt 2 3110000 +3 halt 3 3120000 +3 halt 4 3110000 +3 halt 5 3120000 +3 halt 6 3110000 +3 halt 7 3110000 +3 halt 8 3110000 +3 halt 9 3110000 +3 halt 10 3110000 +3 halt 11 3120000 +3 halt 12 3110000 +3 halt 13 3110000 +3 halt 14 3110000 +3 halt 15 3110000 +3 halt 16 3110000 +3 halt 17 3100000 +3 halt 18 3110000 +3 halt 19 3110000 +3 halt 20 3130000 +3 halt 21 3110000 +3 halt 22 3110000 +3 halt 23 3110000 +3 halt 24 3110000 +3 halt 25 3110000 +3 halt 26 3110000 +3 halt 27 3110000 +3 halt 28 3120000 +3 halt 29 3130000 +3 halt 30 3100000 +# point pass=3 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997949 +3 run 1 3110000 +3 run 2 3120000 +3 run 3 3120000 +3 run 4 3120000 +3 run 5 3120000 +3 run 6 3110000 +3 run 7 3110000 +3 run 8 3120000 +3 run 9 3110000 +3 run 10 3120000 +3 run 11 3110000 +3 run 12 3110000 +3 run 13 3120000 +3 run 14 3110000 +3 run 15 3110000 +3 run 16 3130000 +3 run 17 3140000 +3 run 18 3120000 +3 run 19 3110000 +3 run 20 3110000 +3 run 21 3100000 +3 run 22 3120000 +3 run 23 3110000 +3 run 24 3120000 +3 run 25 3140000 +3 run 26 3110000 +3 run 27 3110000 +3 run 28 3120000 +3 run 29 3120000 +3 run 30 3100000 +# point pass=3 state=run jobs_per_s=64976.0 updates_per_s=33267680.8 clock_hz=99997951 +4 halt 1 3110000 +4 halt 2 3110000 +4 halt 3 3110000 +4 halt 4 3100000 +4 halt 5 3130000 +4 halt 6 3110000 +4 halt 7 3110000 +4 halt 8 3120000 +4 halt 9 3110000 +4 halt 10 3110000 +4 halt 11 3120000 +4 halt 12 3110000 +4 halt 13 3110000 +4 halt 14 3130000 +4 halt 15 3110000 +4 halt 16 3110000 +4 halt 17 3110000 +4 halt 18 3140000 +4 halt 19 3110000 +4 halt 20 3110000 +4 halt 21 3100000 +4 halt 22 3120000 +4 halt 23 3110000 +4 halt 24 3110000 +4 halt 25 3110000 +4 halt 26 3110000 +4 halt 27 3110000 +4 halt 28 3110000 +4 halt 29 3110000 +4 halt 30 3110000 +# point pass=4 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997953 +4 run 1 3110000 +4 run 2 3140000 +4 run 3 3130000 +4 run 4 3130000 +4 run 5 3110000 +4 run 6 3110000 +4 run 7 3110000 +4 run 8 3110000 +4 run 9 3110000 +4 run 10 3140000 +4 run 11 3120000 +4 run 12 3110000 +4 run 13 3120000 +4 run 14 3110000 +4 run 15 3110000 +4 run 16 3110000 +4 run 17 3110000 +4 run 18 3110000 +4 run 19 3110000 +4 run 20 3110000 +4 run 21 3120000 +4 run 22 3100000 +4 run 23 3110000 +4 run 24 3120000 +4 run 25 3120000 +4 run 26 3100000 +4 run 27 3110000 +4 run 28 3120000 +4 run 29 3120000 +4 run 30 3110000 +# point pass=4 state=run jobs_per_s=64976.0 updates_per_s=33267681.8 clock_hz=99997954 +5 halt 1 3110000 +5 halt 2 3110000 +5 halt 3 3110000 +5 halt 4 3110000 +5 halt 5 3120000 +5 halt 6 3120000 +5 halt 7 3130000 +5 halt 8 3110000 +5 halt 9 3120000 +5 halt 10 3110000 +5 halt 11 3110000 +5 halt 12 3110000 +5 halt 13 3110000 +5 halt 14 3120000 +5 halt 15 3110000 +5 halt 16 3120000 +5 halt 17 3110000 +5 halt 18 3120000 +5 halt 19 3110000 +5 halt 20 3110000 +5 halt 21 3120000 +5 halt 22 3130000 +5 halt 23 3120000 +5 halt 24 3110000 +5 halt 25 3110000 +5 halt 26 3110000 +5 halt 27 3110000 +5 halt 28 3120000 +5 halt 29 3120000 +5 halt 30 3130000 +# point pass=5 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997955 +5 run 1 3110000 +5 run 2 3110000 +5 run 3 3110000 +5 run 4 3130000 +5 run 5 3110000 +5 run 6 3110000 +5 run 7 3110000 +5 run 8 3110000 +5 run 9 3270000 +5 run 10 3100000 +5 run 11 3110000 +5 run 12 3110000 +5 run 13 3110000 +5 run 14 3110000 +5 run 15 3110000 +5 run 16 3110000 +5 run 17 3110000 +5 run 18 3110000 +5 run 19 3120000 +5 run 20 3110000 +5 run 21 3100000 +5 run 22 3120000 +5 run 23 3120000 +5 run 24 3100000 +5 run 25 3100000 +5 run 26 3120000 +5 run 27 3120000 +5 run 28 3110000 +5 run 29 3110000 +5 run 30 3120000 +# point pass=5 state=run jobs_per_s=64975.9 updates_per_s=33267682.4 clock_hz=99997956 +6 run 1 3120000 +6 run 2 3120000 +6 run 3 3110000 +6 run 4 3140000 +6 run 5 3120000 +6 run 6 3130000 +6 run 7 3140000 +6 run 8 3120000 +6 run 9 3120000 +6 run 10 3120000 +6 run 11 3120000 +6 run 12 3140000 +6 run 13 3120000 +6 run 14 3120000 +6 run 15 3150000 +6 run 16 3140000 +6 run 17 3130000 +6 run 18 3130000 +6 run 19 3120000 +6 run 20 3120000 +6 run 21 3120000 +6 run 22 3130000 +6 run 23 3120000 +6 run 24 3130000 +6 run 25 3120000 +6 run 26 3120000 +6 run 27 3110000 +6 run 28 3110000 +6 run 29 3120000 +6 run 30 3120000 +# point pass=6 state=run jobs_per_s=64976.0 updates_per_s=33267682.5 clock_hz=99997957 +6 halt 1 3110000 +6 halt 2 3120000 +6 halt 3 3110000 +6 halt 4 3130000 +6 halt 5 3140000 +6 halt 6 3120000 +6 halt 7 3110000 +6 halt 8 3110000 +6 halt 9 3120000 +6 halt 10 3120000 +6 halt 11 3120000 +6 halt 12 3110000 +6 halt 13 3140000 +6 halt 14 3110000 +6 halt 15 3120000 +6 halt 16 3110000 +6 halt 17 3110000 +6 halt 18 3100000 +6 halt 19 3110000 +6 halt 20 3110000 +6 halt 21 3100000 +6 halt 22 3100000 +6 halt 23 3110000 +6 halt 24 3110000 +6 halt 25 3110000 +6 halt 26 3110000 +6 halt 27 3100000 +6 halt 28 3120000 +6 halt 29 3110000 +6 halt 30 3110000 +# point pass=6 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997957 +7 run 1 3140000 +7 run 2 3130000 +7 run 3 3120000 +7 run 4 3120000 +7 run 5 3120000 +7 run 6 3130000 +7 run 7 3130000 +7 run 8 3110000 +7 run 9 3110000 +7 run 10 3120000 +7 run 11 3110000 +7 run 12 3110000 +7 run 13 3110000 +7 run 14 3120000 +7 run 15 3110000 +7 run 16 3110000 +7 run 17 3110000 +7 run 18 3120000 +7 run 19 3450000 +7 run 20 3110000 +7 run 21 3110000 +7 run 22 3120000 +7 run 23 3110000 +7 run 24 3170000 +7 run 25 3140000 +7 run 26 3120000 +7 run 27 3110000 +7 run 28 3120000 +7 run 29 3110000 +7 run 30 3120000 +# point pass=7 state=run jobs_per_s=64976.0 updates_per_s=33267683.0 clock_hz=99997958 +7 halt 1 3120000 +7 halt 2 3110000 +7 halt 3 3110000 +7 halt 4 3120000 +7 halt 5 3110000 +7 halt 6 3130000 +7 halt 7 3110000 +7 halt 8 3110000 +7 halt 9 3110000 +7 halt 10 3110000 +7 halt 11 3110000 +7 halt 12 3120000 +7 halt 13 3150000 +7 halt 14 3120000 +7 halt 15 3120000 +7 halt 16 3120000 +7 halt 17 3110000 +7 halt 18 3120000 +7 halt 19 3120000 +7 halt 20 3110000 +7 halt 21 3140000 +7 halt 22 3130000 +7 halt 23 3120000 +7 halt 24 3120000 +7 halt 25 3120000 +7 halt 26 3120000 +7 halt 27 3120000 +7 halt 28 3110000 +7 halt 29 3110000 +7 halt 30 3130000 +# point pass=7 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997959 +8 halt 1 3120000 +8 halt 2 3120000 +8 halt 3 3120000 +8 halt 4 3110000 +8 halt 5 3110000 +8 halt 6 3110000 +8 halt 7 3120000 +8 halt 8 3110000 +8 halt 9 3120000 +8 halt 10 3140000 +8 halt 11 3120000 +8 halt 12 3110000 +8 halt 13 3110000 +8 halt 14 3110000 +8 halt 15 3110000 +8 halt 16 3120000 +8 halt 17 3130000 +8 halt 18 3120000 +8 halt 19 3130000 +8 halt 20 3110000 +8 halt 21 3110000 +8 halt 22 3110000 +8 halt 23 3120000 +8 halt 24 3120000 +8 halt 25 3110000 +8 halt 26 3120000 +8 halt 27 3120000 +8 halt 28 3110000 +8 halt 29 3110000 +8 halt 30 3120000 +# point pass=8 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997959 +8 run 1 3120000 +8 run 2 3120000 +8 run 3 3120000 +8 run 4 3110000 +8 run 5 3120000 +8 run 6 3130000 +8 run 7 3140000 +8 run 8 3120000 +8 run 9 3130000 +8 run 10 3110000 +8 run 11 3120000 +8 run 12 3110000 +8 run 13 3120000 +8 run 14 3120000 +8 run 15 3140000 +8 run 16 3120000 +8 run 17 3130000 +8 run 18 3110000 +8 run 19 3120000 +8 run 20 3130000 +8 run 21 3130000 +8 run 22 3120000 +8 run 23 3120000 +8 run 24 3120000 +8 run 25 3120000 +8 run 26 3120000 +8 run 27 3130000 +8 run 28 3120000 +8 run 29 3120000 +8 run 30 3140000 +# point pass=8 state=run jobs_per_s=64975.9 updates_per_s=33267683.7 clock_hz=99997960 +9 halt 1 3120000 +9 halt 2 3110000 +9 halt 3 3130000 +9 halt 4 3120000 +9 halt 5 3110000 +9 halt 6 3110000 +9 halt 7 3110000 +9 halt 8 3110000 +9 halt 9 3120000 +9 halt 10 3110000 +9 halt 11 3110000 +9 halt 12 3130000 +9 halt 13 3110000 +9 halt 14 3120000 +9 halt 15 3110000 +9 halt 16 3110000 +9 halt 17 3110000 +9 halt 18 3110000 +9 halt 19 3120000 +9 halt 20 3110000 +9 halt 21 3110000 +9 halt 22 3110000 +9 halt 23 3110000 +9 halt 24 3110000 +9 halt 25 3110000 +9 halt 26 3110000 +9 halt 27 3130000 +9 halt 28 3110000 +9 halt 29 3100000 +9 halt 30 3110000 +# point pass=9 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997960 +9 run 1 3130000 +9 run 2 3120000 +9 run 3 3110000 +9 run 4 3120000 +9 run 5 3110000 +9 run 6 3120000 +9 run 7 3120000 +9 run 8 3130000 +9 run 9 3130000 +9 run 10 3110000 +9 run 11 3110000 +9 run 12 3120000 +9 run 13 3120000 +9 run 14 3130000 +9 run 15 3130000 +9 run 16 3120000 +9 run 17 3120000 +9 run 18 3120000 +9 run 19 3120000 +9 run 20 3120000 +9 run 21 3120000 +9 run 22 3110000 +9 run 23 3130000 +9 run 24 3140000 +9 run 25 3120000 +9 run 26 3120000 +9 run 27 3110000 +9 run 28 3120000 +9 run 29 3110000 +9 run 30 3110000 +# point pass=9 state=run jobs_per_s=64976.0 updates_per_s=33267683.6 clock_hz=99997960 +10 run 1 3120000 +10 run 2 3110000 +10 run 3 3380000 +10 run 4 3110000 +10 run 5 3110000 +10 run 6 3110000 +10 run 7 3100000 +10 run 8 3110000 +10 run 9 3110000 +10 run 10 3110000 +10 run 11 3100000 +10 run 12 3130000 +10 run 13 3110000 +10 run 14 3110000 +10 run 15 3110000 +10 run 16 3110000 +10 run 17 3110000 +10 run 18 3110000 +10 run 19 3110000 +10 run 20 3120000 +10 run 21 3130000 +10 run 22 3110000 +10 run 23 3110000 +10 run 24 3110000 +10 run 25 3110000 +10 run 26 3110000 +10 run 27 3110000 +10 run 28 3100000 +10 run 29 3120000 +10 run 30 3110000 +# point pass=10 state=run jobs_per_s=64975.9 updates_per_s=33267683.9 clock_hz=99997961 +10 halt 1 3100000 +10 halt 2 3100000 +10 halt 3 3110000 +10 halt 4 3100000 +10 halt 5 3100000 +10 halt 6 3100000 +10 halt 7 3110000 +10 halt 8 3100000 +10 halt 9 3120000 +10 halt 10 3110000 +10 halt 11 3120000 +10 halt 12 3100000 +10 halt 13 3100000 +10 halt 14 3100000 +10 halt 15 3110000 +10 halt 16 3120000 +10 halt 17 3120000 +10 halt 18 3110000 +10 halt 19 3100000 +10 halt 20 3110000 +10 halt 21 3100000 +10 halt 22 3110000 +10 halt 23 3100000 +10 halt 24 3100000 +10 halt 25 3110000 +10 halt 26 3110000 +10 halt 27 3100000 +10 halt 28 3100000 +10 halt 29 3100000 +10 halt 30 3090000 +# point pass=10 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997961 +11 halt 1 3120000 +11 halt 2 3110000 +11 halt 3 3090000 +11 halt 4 3100000 +11 halt 5 3130000 +11 halt 6 3110000 +11 halt 7 3100000 +11 halt 8 3110000 +11 halt 9 3100000 +11 halt 10 3090000 +11 halt 11 3100000 +11 halt 12 3110000 +11 halt 13 3100000 +11 halt 14 3120000 +11 halt 15 3100000 +11 halt 16 3120000 +11 halt 17 3100000 +11 halt 18 3100000 +11 halt 19 3100000 +11 halt 20 3100000 +11 halt 21 3100000 +11 halt 22 3110000 +11 halt 23 3100000 +11 halt 24 3110000 +11 halt 25 3100000 +11 halt 26 3110000 +11 halt 27 3100000 +11 halt 28 3110000 +11 halt 29 3110000 +11 halt 30 3110000 +# point pass=11 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997961 +11 run 1 3110000 +11 run 2 3140000 +11 run 3 3150000 +11 run 4 3120000 +11 run 5 3120000 +11 run 6 3120000 +11 run 7 3120000 +11 run 8 3120000 +11 run 9 3120000 +11 run 10 3120000 +11 run 11 3130000 +11 run 12 3110000 +11 run 13 3130000 +11 run 14 3120000 +11 run 15 3130000 +11 run 16 3120000 +11 run 17 3130000 +11 run 18 3120000 +11 run 19 3120000 +11 run 20 3120000 +11 run 21 3120000 +11 run 22 3130000 +11 run 23 3120000 +11 run 24 3120000 +11 run 25 3120000 +11 run 26 3140000 +11 run 27 3120000 +11 run 28 3120000 +11 run 29 3120000 +11 run 30 3130000 +# point pass=11 state=run jobs_per_s=64976.0 updates_per_s=33267683.6 clock_hz=99997960 +12 halt 1 3110000 +12 halt 2 3110000 +12 halt 3 3110000 +12 halt 4 3110000 +12 halt 5 3110000 +12 halt 6 3100000 +12 halt 7 3110000 +12 halt 8 3100000 +12 halt 9 3100000 +12 halt 10 3110000 +12 halt 11 3110000 +12 halt 12 3110000 +12 halt 13 3110000 +12 halt 14 3120000 +12 halt 15 3110000 +12 halt 16 3100000 +12 halt 17 3110000 +12 halt 18 3110000 +12 halt 19 3110000 +12 halt 20 3100000 +12 halt 21 3120000 +12 halt 22 3120000 +12 halt 23 3130000 +12 halt 24 3100000 +12 halt 25 3110000 +12 halt 26 3100000 +12 halt 27 3110000 +12 halt 28 3110000 +12 halt 29 3110000 +12 halt 30 3110000 +# point pass=12 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997961 +12 run 1 3110000 +12 run 2 3110000 +12 run 3 3110000 +12 run 4 3130000 +12 run 5 3110000 +12 run 6 3110000 +12 run 7 3110000 +12 run 8 3110000 +12 run 9 3120000 +12 run 10 3110000 +12 run 11 3150000 +12 run 12 3110000 +12 run 13 3120000 +12 run 14 3110000 +12 run 15 3120000 +12 run 16 3120000 +12 run 17 3110000 +12 run 18 3120000 +12 run 19 3130000 +12 run 20 3140000 +12 run 21 3120000 +12 run 22 3110000 +12 run 23 3120000 +12 run 24 3110000 +12 run 25 3120000 +12 run 26 3120000 +12 run 27 3120000 +12 run 28 3120000 +12 run 29 3110000 +12 run 30 3110000 +# point pass=12 state=run jobs_per_s=64976.0 updates_per_s=33267687.8 clock_hz=99997972 +# done SystemTime { tv_sec: 1793915273, tv_nsec: 319036681 } +# passes used 12 of 12 (dropped: []) +# run - halt = 6.7 +/- 1.8 mW (per-pass sd 6.2); 8/12 passes positive; 5 sample excursions beyond 6 MAD +# per-pass differences (mW): 10.0 10.0 5.0 0.0 0.0 10.0 0.0 0.0 10.0 10.0 20.0 5.0 +# board power while running 3117 mW; jobs/s 64976.0; updates/s 33267683.1; measured PL clock 99.998 MHz +# pre-registered prediction 5.0 mW: measured/predicted = 1.33, 0.9 sigma from it diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/k1_rdet.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/k1_rdet.dat new file mode 100644 index 000000000..660172d76 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/k1_rdet.dat @@ -0,0 +1,723 @@ +# kv_runhalt bitstream=ev_rdet.bit.bin gpio=594 settle=60s passes=12 samples=30 +# pass vehicle sample power_uW +1 run 1 3180000 +1 run 2 3170000 +1 run 3 3170000 +1 run 4 3160000 +1 run 5 3170000 +1 run 6 3150000 +1 run 7 3180000 +1 run 8 3150000 +1 run 9 3190000 +1 run 10 3140000 +1 run 11 3190000 +1 run 12 3140000 +1 run 13 3180000 +1 run 14 3140000 +1 run 15 3190000 +1 run 16 3140000 +1 run 17 3210000 +1 run 18 3140000 +1 run 19 3210000 +1 run 20 3140000 +1 run 21 3230000 +1 run 22 3150000 +1 run 23 3220000 +1 run 24 3150000 +1 run 25 3190000 +1 run 26 3140000 +1 run 27 3190000 +1 run 28 3150000 +1 run 29 3190000 +1 run 30 3140000 +1 halt 1 3070000 +1 halt 2 3080000 +1 halt 3 3080000 +1 halt 4 3110000 +1 halt 5 3070000 +1 halt 6 3080000 +1 halt 7 3080000 +1 halt 8 3070000 +1 halt 9 3080000 +1 halt 10 3090000 +1 halt 11 3080000 +1 halt 12 3080000 +1 halt 13 3080000 +1 halt 14 3080000 +1 halt 15 3140000 +1 halt 16 3080000 +1 halt 17 3100000 +1 halt 18 3080000 +1 halt 19 3080000 +1 halt 20 3090000 +1 halt 21 3090000 +1 halt 22 3080000 +1 halt 23 3140000 +1 halt 24 3100000 +1 halt 25 3100000 +1 halt 26 3080000 +1 halt 27 3090000 +1 halt 28 3080000 +1 halt 29 3110000 +1 halt 30 3080000 +2 run 1 3150000 +2 run 2 3150000 +2 run 3 3160000 +2 run 4 3150000 +2 run 5 3150000 +2 run 6 3140000 +2 run 7 3150000 +2 run 8 3150000 +2 run 9 3150000 +2 run 10 3150000 +2 run 11 3160000 +2 run 12 3170000 +2 run 13 3170000 +2 run 14 3150000 +2 run 15 3160000 +2 run 16 3150000 +2 run 17 3190000 +2 run 18 3150000 +2 run 19 3170000 +2 run 20 3140000 +2 run 21 3160000 +2 run 22 3160000 +2 run 23 3150000 +2 run 24 3150000 +2 run 25 3150000 +2 run 26 3160000 +2 run 27 3170000 +2 run 28 3160000 +2 run 29 3160000 +2 run 30 3150000 +2 halt 1 3190000 +2 halt 2 3100000 +2 halt 3 3170000 +2 halt 4 3080000 +2 halt 5 3180000 +2 halt 6 3080000 +2 halt 7 3210000 +2 halt 8 3090000 +2 halt 9 3180000 +2 halt 10 3080000 +2 halt 11 3160000 +2 halt 12 3080000 +2 halt 13 3080000 +2 halt 14 3200000 +2 halt 15 3090000 +2 halt 16 3190000 +2 halt 17 3080000 +2 halt 18 3210000 +2 halt 19 3080000 +2 halt 20 3200000 +2 halt 21 3100000 +2 halt 22 3220000 +2 halt 23 3080000 +2 halt 24 3200000 +2 halt 25 3080000 +2 halt 26 3220000 +2 halt 27 3080000 +2 halt 28 3230000 +2 halt 29 3080000 +2 halt 30 3090000 +3 run 1 3150000 +3 run 2 3240000 +3 run 3 3150000 +3 run 4 3240000 +3 run 5 3140000 +3 run 6 3250000 +3 run 7 3140000 +3 run 8 3240000 +3 run 9 3140000 +3 run 10 3250000 +3 run 11 3140000 +3 run 12 3240000 +3 run 13 3140000 +3 run 14 3240000 +3 run 15 3140000 +3 run 16 3240000 +3 run 17 3150000 +3 run 18 3250000 +3 run 19 3150000 +3 run 20 3250000 +3 run 21 3140000 +3 run 22 3260000 +3 run 23 3160000 +3 run 24 3250000 +3 run 25 3140000 +3 run 26 3260000 +3 run 27 3150000 +3 run 28 3260000 +3 run 29 3160000 +3 run 30 3280000 +3 halt 1 3170000 +3 halt 2 3070000 +3 halt 3 3180000 +3 halt 4 3090000 +3 halt 5 3160000 +3 halt 6 3090000 +3 halt 7 3170000 +3 halt 8 3080000 +3 halt 9 3150000 +3 halt 10 3090000 +3 halt 11 3140000 +3 halt 12 3100000 +3 halt 13 3130000 +3 halt 14 3080000 +3 halt 15 3130000 +3 halt 16 3080000 +3 halt 17 3160000 +3 halt 18 3100000 +3 halt 19 3140000 +3 halt 20 3100000 +3 halt 21 3160000 +3 halt 22 3090000 +3 halt 23 3170000 +3 halt 24 3080000 +3 halt 25 3160000 +3 halt 26 3090000 +3 halt 27 3170000 +3 halt 28 3100000 +3 halt 29 3170000 +3 halt 30 3090000 +4 halt 1 3100000 +4 halt 2 3170000 +4 halt 3 3090000 +4 halt 4 3190000 +4 halt 5 3090000 +4 halt 6 3190000 +4 halt 7 3100000 +4 halt 8 3170000 +4 halt 9 3080000 +4 halt 10 3220000 +4 halt 11 3080000 +4 halt 12 3240000 +4 halt 13 3090000 +4 halt 14 3230000 +4 halt 15 3090000 +4 halt 16 3250000 +4 halt 17 3090000 +4 halt 18 3240000 +4 halt 19 3080000 +4 halt 20 3270000 +4 halt 21 3480000 +4 halt 22 3080000 +4 halt 23 3090000 +4 halt 24 3100000 +4 halt 25 3090000 +4 halt 26 3110000 +4 halt 27 3080000 +4 halt 28 3080000 +4 halt 29 3090000 +4 halt 30 3090000 +4 run 1 3150000 +4 run 2 3150000 +4 run 3 3150000 +4 run 4 3140000 +4 run 5 3150000 +4 run 6 3160000 +4 run 7 3150000 +4 run 8 3150000 +4 run 9 3150000 +4 run 10 3160000 +4 run 11 3150000 +4 run 12 3150000 +4 run 13 3150000 +4 run 14 3160000 +4 run 15 3150000 +4 run 16 3190000 +4 run 17 3140000 +4 run 18 3190000 +4 run 19 3140000 +4 run 20 3180000 +4 run 21 3150000 +4 run 22 3180000 +4 run 23 3160000 +4 run 24 3170000 +4 run 25 3140000 +4 run 26 3180000 +4 run 27 3140000 +4 run 28 3150000 +4 run 29 3180000 +4 run 30 3160000 +5 run 1 3160000 +5 run 2 3160000 +5 run 3 3150000 +5 run 4 3170000 +5 run 5 3150000 +5 run 6 3160000 +5 run 7 3150000 +5 run 8 3150000 +5 run 9 3150000 +5 run 10 3170000 +5 run 11 3160000 +5 run 12 3180000 +5 run 13 3150000 +5 run 14 3160000 +5 run 15 3150000 +5 run 16 3180000 +5 run 17 3150000 +5 run 18 3200000 +5 run 19 3150000 +5 run 20 3180000 +5 run 21 3160000 +5 run 22 3180000 +5 run 23 3150000 +5 run 24 3180000 +5 run 25 3160000 +5 run 26 3210000 +5 run 27 3160000 +5 run 28 3190000 +5 run 29 3160000 +5 run 30 3200000 +5 halt 1 3170000 +5 halt 2 3090000 +5 halt 3 3160000 +5 halt 4 3070000 +5 halt 5 3140000 +5 halt 6 3090000 +5 halt 7 3150000 +5 halt 8 3070000 +5 halt 9 3130000 +5 halt 10 3070000 +5 halt 11 3160000 +5 halt 12 3070000 +5 halt 13 3150000 +5 halt 14 3070000 +5 halt 15 3160000 +5 halt 16 3080000 +5 halt 17 3160000 +5 halt 18 3070000 +5 halt 19 3160000 +5 halt 20 3070000 +5 halt 21 3190000 +5 halt 22 3080000 +5 halt 23 3170000 +5 halt 24 3070000 +5 halt 25 3170000 +5 halt 26 3070000 +5 halt 27 3170000 +5 halt 28 3070000 +5 halt 29 3170000 +5 halt 30 3070000 +6 run 1 3250000 +6 run 2 3150000 +6 run 3 3240000 +6 run 4 3150000 +6 run 5 3250000 +6 run 6 3150000 +6 run 7 3240000 +6 run 8 3150000 +6 run 9 3250000 +6 run 10 3160000 +6 run 11 3240000 +6 run 12 3150000 +6 run 13 3240000 +6 run 14 3150000 +6 run 15 3240000 +6 run 16 3160000 +6 run 17 3240000 +6 run 18 3150000 +6 run 19 3250000 +6 run 20 3160000 +6 run 21 3230000 +6 run 22 3150000 +6 run 23 3220000 +6 run 24 3150000 +6 run 25 3210000 +6 run 26 3150000 +6 run 27 3220000 +6 run 28 3150000 +6 run 29 3260000 +6 run 30 3150000 +6 halt 1 3070000 +6 halt 2 3070000 +6 halt 3 3080000 +6 halt 4 3080000 +6 halt 5 3070000 +6 halt 6 3080000 +6 halt 7 3070000 +6 halt 8 3070000 +6 halt 9 3080000 +6 halt 10 3100000 +6 halt 11 3070000 +6 halt 12 3120000 +6 halt 13 3070000 +6 halt 14 3090000 +6 halt 15 3070000 +6 halt 16 3120000 +6 halt 17 3070000 +6 halt 18 3130000 +6 halt 19 3070000 +6 halt 20 3110000 +6 halt 21 3080000 +6 halt 22 3120000 +6 halt 23 3070000 +6 halt 24 3100000 +6 halt 25 3070000 +6 halt 26 3110000 +6 halt 27 3070000 +6 halt 28 3130000 +6 halt 29 3070000 +6 halt 30 3140000 +7 halt 1 3090000 +7 halt 2 3080000 +7 halt 3 3070000 +7 halt 4 3070000 +7 halt 5 3070000 +7 halt 6 3070000 +7 halt 7 3250000 +7 halt 8 3070000 +7 halt 9 3080000 +7 halt 10 3070000 +7 halt 11 3240000 +7 halt 12 3070000 +7 halt 13 3230000 +7 halt 14 3070000 +7 halt 15 3220000 +7 halt 16 3080000 +7 halt 17 3210000 +7 halt 18 3070000 +7 halt 19 3200000 +7 halt 20 3080000 +7 halt 21 3200000 +7 halt 22 3070000 +7 halt 23 3210000 +7 halt 24 3070000 +7 halt 25 3190000 +7 halt 26 3080000 +7 halt 27 3220000 +7 halt 28 3070000 +7 halt 29 3180000 +7 halt 30 3080000 +7 run 1 3140000 +7 run 2 3210000 +7 run 3 3140000 +7 run 4 3210000 +7 run 5 3140000 +7 run 6 3210000 +7 run 7 3140000 +7 run 8 3220000 +7 run 9 3150000 +7 run 10 3230000 +7 run 11 3140000 +7 run 12 3220000 +7 run 13 3140000 +7 run 14 3210000 +7 run 15 3150000 +7 run 16 3230000 +7 run 17 3150000 +7 run 18 3220000 +7 run 19 3150000 +7 run 20 3200000 +7 run 21 3150000 +7 run 22 3210000 +7 run 23 3160000 +7 run 24 3210000 +7 run 25 3150000 +7 run 26 3220000 +7 run 27 3150000 +7 run 28 3200000 +7 run 29 3160000 +7 run 30 3190000 +8 halt 1 3070000 +8 halt 2 3070000 +8 halt 3 3080000 +8 halt 4 3070000 +8 halt 5 3240000 +8 halt 6 3070000 +8 halt 7 3210000 +8 halt 8 3080000 +8 halt 9 3200000 +8 halt 10 3080000 +8 halt 11 3210000 +8 halt 12 3090000 +8 halt 13 3210000 +8 halt 14 3080000 +8 halt 15 3540000 +8 halt 16 3080000 +8 halt 17 3170000 +8 halt 18 3100000 +8 halt 19 3160000 +8 halt 20 3140000 +8 halt 21 3190000 +8 halt 22 3080000 +8 halt 23 3190000 +8 halt 24 3070000 +8 halt 25 3170000 +8 halt 26 3080000 +8 halt 27 3170000 +8 halt 28 3080000 +8 halt 29 3160000 +8 halt 30 3080000 +8 run 1 3280000 +8 run 2 3130000 +8 run 3 3250000 +8 run 4 3140000 +8 run 5 3260000 +8 run 6 3140000 +8 run 7 3260000 +8 run 8 3150000 +8 run 9 3240000 +8 run 10 3140000 +8 run 11 3260000 +8 run 12 3140000 +8 run 13 3260000 +8 run 14 3140000 +8 run 15 3260000 +8 run 16 3140000 +8 run 17 3270000 +8 run 18 3140000 +8 run 19 3260000 +8 run 20 3150000 +8 run 21 3280000 +8 run 22 3150000 +8 run 23 3260000 +8 run 24 3140000 +8 run 25 3270000 +8 run 26 3140000 +8 run 27 3290000 +8 run 28 3140000 +8 run 29 3290000 +8 run 30 3150000 +9 halt 1 3170000 +9 halt 2 3090000 +9 halt 3 3170000 +9 halt 4 3080000 +9 halt 5 3180000 +9 halt 6 3080000 +9 halt 7 3170000 +9 halt 8 3070000 +9 halt 9 3190000 +9 halt 10 3090000 +9 halt 11 3160000 +9 halt 12 3080000 +9 halt 13 3190000 +9 halt 14 3080000 +9 halt 15 3180000 +9 halt 16 3080000 +9 halt 17 3160000 +9 halt 18 3080000 +9 halt 19 3170000 +9 halt 20 3090000 +9 halt 21 3170000 +9 halt 22 3090000 +9 halt 23 3170000 +9 halt 24 3080000 +9 halt 25 3180000 +9 halt 26 3090000 +9 halt 27 3190000 +9 halt 28 3080000 +9 halt 29 3220000 +9 halt 30 3080000 +9 run 1 3150000 +9 run 2 3190000 +9 run 3 3140000 +9 run 4 3220000 +9 run 5 3140000 +9 run 6 3220000 +9 run 7 3140000 +9 run 8 3220000 +9 run 9 3140000 +9 run 10 3220000 +9 run 11 3150000 +9 run 12 3240000 +9 run 13 3140000 +9 run 14 3230000 +9 run 15 3140000 +9 run 16 3230000 +9 run 17 3130000 +9 run 18 3230000 +9 run 19 3140000 +9 run 20 3210000 +9 run 21 3150000 +9 run 22 3230000 +9 run 23 3160000 +9 run 24 3240000 +9 run 25 3160000 +9 run 26 3240000 +9 run 27 3150000 +9 run 28 3240000 +9 run 29 3150000 +9 run 30 3240000 +10 halt 1 3080000 +10 halt 2 3110000 +10 halt 3 3070000 +10 halt 4 3130000 +10 halt 5 3070000 +10 halt 6 3150000 +10 halt 7 3080000 +10 halt 8 3130000 +10 halt 9 3070000 +10 halt 10 3120000 +10 halt 11 3070000 +10 halt 12 3130000 +10 halt 13 3080000 +10 halt 14 3110000 +10 halt 15 3070000 +10 halt 16 3110000 +10 halt 17 3070000 +10 halt 18 3120000 +10 halt 19 3070000 +10 halt 20 3100000 +10 halt 21 3080000 +10 halt 22 3120000 +10 halt 23 3070000 +10 halt 24 3110000 +10 halt 25 3170000 +10 halt 26 3070000 +10 halt 27 3170000 +10 halt 28 3100000 +10 halt 29 3170000 +10 halt 30 3080000 +10 run 1 3150000 +10 run 2 3180000 +10 run 3 3150000 +10 run 4 3180000 +10 run 5 3160000 +10 run 6 3170000 +10 run 7 3160000 +10 run 8 3190000 +10 run 9 3150000 +10 run 10 3200000 +10 run 11 3150000 +10 run 12 3210000 +10 run 13 3150000 +10 run 14 3200000 +10 run 15 3150000 +10 run 16 3240000 +10 run 17 3160000 +10 run 18 3240000 +10 run 19 3150000 +10 run 20 3240000 +10 run 21 3160000 +10 run 22 3240000 +10 run 23 3150000 +10 run 24 3250000 +10 run 25 3270000 +10 run 26 3150000 +10 run 27 3280000 +10 run 28 3150000 +10 run 29 3320000 +10 run 30 3160000 +11 halt 1 3180000 +11 halt 2 3090000 +11 halt 3 3180000 +11 halt 4 3090000 +11 halt 5 3200000 +11 halt 6 3080000 +11 halt 7 3190000 +11 halt 8 3080000 +11 halt 9 3180000 +11 halt 10 3090000 +11 halt 11 3170000 +11 halt 12 3120000 +11 halt 13 3180000 +11 halt 14 3090000 +11 halt 15 3180000 +11 halt 16 3110000 +11 halt 17 3170000 +11 halt 18 3110000 +11 halt 19 3190000 +11 halt 20 3090000 +11 halt 21 3170000 +11 halt 22 3090000 +11 halt 23 3170000 +11 halt 24 3100000 +11 halt 25 3170000 +11 halt 26 3100000 +11 halt 27 3170000 +11 halt 28 3090000 +11 halt 29 3160000 +11 halt 30 3090000 +11 run 1 3160000 +11 run 2 3160000 +11 run 3 3160000 +11 run 4 3150000 +11 run 5 3160000 +11 run 6 3160000 +11 run 7 3150000 +11 run 8 3170000 +11 run 9 3180000 +11 run 10 3180000 +11 run 11 3160000 +11 run 12 3180000 +11 run 13 3160000 +11 run 14 3170000 +11 run 15 3150000 +11 run 16 3180000 +11 run 17 3150000 +11 run 18 3190000 +11 run 19 3170000 +11 run 20 3190000 +11 run 21 3160000 +11 run 22 3220000 +11 run 23 3150000 +11 run 24 3220000 +11 run 25 3160000 +11 run 26 3220000 +11 run 27 3160000 +11 run 28 3230000 +11 run 29 3150000 +11 run 30 3220000 +12 halt 1 3080000 +12 halt 2 3080000 +12 halt 3 3080000 +12 halt 4 3080000 +12 halt 5 3080000 +12 halt 6 3090000 +12 halt 7 3070000 +12 halt 8 3080000 +12 halt 9 3070000 +12 halt 10 3070000 +12 halt 11 3100000 +12 halt 12 3070000 +12 halt 13 3070000 +12 halt 14 3190000 +12 halt 15 3080000 +12 halt 16 3070000 +12 halt 17 3100000 +12 halt 18 3070000 +12 halt 19 3090000 +12 halt 20 3070000 +12 halt 21 3070000 +12 halt 22 3070000 +12 halt 23 3070000 +12 halt 24 3080000 +12 halt 25 3070000 +12 halt 26 3070000 +12 halt 27 3080000 +12 halt 28 3080000 +12 halt 29 3080000 +12 halt 30 3090000 +12 run 1 3150000 +12 run 2 3260000 +12 run 3 3150000 +12 run 4 3260000 +12 run 5 3150000 +12 run 6 3290000 +12 run 7 3150000 +12 run 8 3160000 +12 run 9 3150000 +12 run 10 3160000 +12 run 11 3160000 +12 run 12 3170000 +12 run 13 3160000 +12 run 14 3150000 +12 run 15 3150000 +12 run 16 3150000 +12 run 17 3180000 +12 run 18 3150000 +12 run 19 3170000 +12 run 20 3160000 +12 run 21 3150000 +12 run 22 3170000 +12 run 23 3150000 +12 run 24 3200000 +12 run 25 3150000 +12 run 26 3180000 +12 run 27 3150000 +12 run 28 3210000 +12 run 29 3150000 +12 run 30 3190000 +# done diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/null.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/null.dat new file mode 100644 index 000000000..5d1f5056d --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/null.dat @@ -0,0 +1,505 @@ +# class t0-recall +# vqpu-edge meter passes=8 settle=60s samples=30 null=true sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793909509, tv_nsec: 550481081 } +# pass state sample power_uW +1 halt 1 3110000 +1 halt 2 3110000 +1 halt 3 3140000 +1 halt 4 3110000 +1 halt 5 3110000 +1 halt 6 3110000 +1 halt 7 3110000 +1 halt 8 3100000 +1 halt 9 3120000 +1 halt 10 3110000 +1 halt 11 3110000 +1 halt 12 3110000 +1 halt 13 3120000 +1 halt 14 3120000 +1 halt 15 3110000 +1 halt 16 3110000 +1 halt 17 3110000 +1 halt 18 3130000 +1 halt 19 3120000 +1 halt 20 3110000 +1 halt 21 3120000 +1 halt 22 3110000 +1 halt 23 3120000 +1 halt 24 3120000 +1 halt 25 3120000 +1 halt 26 3120000 +1 halt 27 3120000 +1 halt 28 3110000 +1 halt 29 3120000 +1 halt 30 3110000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998042 +1 run 1 3120000 +1 run 2 3110000 +1 run 3 3120000 +1 run 4 3110000 +1 run 5 3110000 +1 run 6 3110000 +1 run 7 3120000 +1 run 8 3110000 +1 run 9 3100000 +1 run 10 3110000 +1 run 11 3110000 +1 run 12 3110000 +1 run 13 3110000 +1 run 14 3120000 +1 run 15 3140000 +1 run 16 3120000 +1 run 17 3110000 +1 run 18 3110000 +1 run 19 3110000 +1 run 20 3110000 +1 run 21 3110000 +1 run 22 3100000 +1 run 23 3120000 +1 run 24 3140000 +1 run 25 3110000 +1 run 26 3110000 +1 run 27 3110000 +1 run 28 3120000 +1 run 29 3100000 +1 run 30 3120000 +# point pass=1 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998030 +2 halt 1 3120000 +2 halt 2 3110000 +2 halt 3 3110000 +2 halt 4 3110000 +2 halt 5 3110000 +2 halt 6 3180000 +2 halt 7 3110000 +2 halt 8 3110000 +2 halt 9 3110000 +2 halt 10 3100000 +2 halt 11 3120000 +2 halt 12 3130000 +2 halt 13 3110000 +2 halt 14 3110000 +2 halt 15 3110000 +2 halt 16 3120000 +2 halt 17 3110000 +2 halt 18 3110000 +2 halt 19 3110000 +2 halt 20 3120000 +2 halt 21 3110000 +2 halt 22 3110000 +2 halt 23 3110000 +2 halt 24 3120000 +2 halt 25 3110000 +2 halt 26 3120000 +2 halt 27 3100000 +2 halt 28 3110000 +2 halt 29 3110000 +2 halt 30 3110000 +# point pass=2 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998021 +2 run 1 3110000 +2 run 2 3110000 +2 run 3 3110000 +2 run 4 3110000 +2 run 5 3110000 +2 run 6 3110000 +2 run 7 3110000 +2 run 8 3120000 +2 run 9 3130000 +2 run 10 3110000 +2 run 11 3110000 +2 run 12 3110000 +2 run 13 3110000 +2 run 14 3110000 +2 run 15 3110000 +2 run 16 3110000 +2 run 17 3130000 +2 run 18 3130000 +2 run 19 3100000 +2 run 20 3110000 +2 run 21 3110000 +2 run 22 3120000 +2 run 23 3110000 +2 run 24 3110000 +2 run 25 3110000 +2 run 26 3120000 +2 run 27 3110000 +2 run 28 3110000 +2 run 29 3110000 +2 run 30 3110000 +# point pass=2 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998014 +3 halt 1 3110000 +3 halt 2 3110000 +3 halt 3 3110000 +3 halt 4 3100000 +3 halt 5 3120000 +3 halt 6 3130000 +3 halt 7 3110000 +3 halt 8 3110000 +3 halt 9 3110000 +3 halt 10 3110000 +3 halt 11 3110000 +3 halt 12 3110000 +3 halt 13 3110000 +3 halt 14 3130000 +3 halt 15 3110000 +3 halt 16 3110000 +3 halt 17 3110000 +3 halt 18 3110000 +3 halt 19 3100000 +3 halt 20 3110000 +3 halt 21 3100000 +3 halt 22 3110000 +3 halt 23 3110000 +3 halt 24 3110000 +3 halt 25 3110000 +3 halt 26 3110000 +3 halt 27 3110000 +3 halt 28 3130000 +3 halt 29 3130000 +3 halt 30 3110000 +# point pass=3 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998007 +3 run 1 3110000 +3 run 2 3130000 +3 run 3 3110000 +3 run 4 3110000 +3 run 5 3110000 +3 run 6 3110000 +3 run 7 3120000 +3 run 8 3110000 +3 run 9 3110000 +3 run 10 3110000 +3 run 11 3110000 +3 run 12 3110000 +3 run 13 3110000 +3 run 14 3110000 +3 run 15 3110000 +3 run 16 3110000 +3 run 17 3120000 +3 run 18 3110000 +3 run 19 3110000 +3 run 20 3110000 +3 run 21 3110000 +3 run 22 3110000 +3 run 23 3110000 +3 run 24 3110000 +3 run 25 3130000 +3 run 26 3130000 +3 run 27 3100000 +3 run 28 3120000 +3 run 29 3110000 +3 run 30 3110000 +# point pass=3 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998002 +4 halt 1 3110000 +4 halt 2 3110000 +4 halt 3 3120000 +4 halt 4 3120000 +4 halt 5 3110000 +4 halt 6 3110000 +4 halt 7 3110000 +4 halt 8 3110000 +4 halt 9 3110000 +4 halt 10 3110000 +4 halt 11 3110000 +4 halt 12 3110000 +4 halt 13 3100000 +4 halt 14 3120000 +4 halt 15 3110000 +4 halt 16 3110000 +4 halt 17 3100000 +4 halt 18 3110000 +4 halt 19 3110000 +4 halt 20 3110000 +4 halt 21 3100000 +4 halt 22 3120000 +4 halt 23 3110000 +4 halt 24 3100000 +4 halt 25 3110000 +4 halt 26 3100000 +4 halt 27 3110000 +4 halt 28 3100000 +4 halt 29 3100000 +4 halt 30 3110000 +# point pass=4 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997997 +4 run 1 3100000 +4 run 2 3110000 +4 run 3 3110000 +4 run 4 3100000 +4 run 5 3110000 +4 run 6 3110000 +4 run 7 3100000 +4 run 8 3110000 +4 run 9 3110000 +4 run 10 3120000 +4 run 11 3120000 +4 run 12 3110000 +4 run 13 3100000 +4 run 14 3110000 +4 run 15 3100000 +4 run 16 3110000 +4 run 17 3110000 +4 run 18 3100000 +4 run 19 3130000 +4 run 20 3110000 +4 run 21 3100000 +4 run 22 3130000 +4 run 23 3120000 +4 run 24 3100000 +4 run 25 3100000 +4 run 26 3110000 +4 run 27 3490000 +4 run 28 3100000 +4 run 29 3110000 +4 run 30 3110000 +# point pass=4 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997993 +5 run 1 3110000 +5 run 2 3110000 +5 run 3 3100000 +5 run 4 3110000 +5 run 5 3110000 +5 run 6 3110000 +5 run 7 3120000 +5 run 8 3140000 +5 run 9 3100000 +5 run 10 3110000 +5 run 11 3110000 +5 run 12 3110000 +5 run 13 3110000 +5 run 14 3110000 +5 run 15 3100000 +5 run 16 3140000 +5 run 17 3110000 +5 run 18 3100000 +5 run 19 3100000 +5 run 20 3110000 +5 run 21 3100000 +5 run 22 3110000 +5 run 23 3110000 +5 run 24 3100000 +5 run 25 3110000 +5 run 26 3100000 +5 run 27 3110000 +5 run 28 3110000 +5 run 29 3110000 +5 run 30 3110000 +# point pass=5 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997990 +5 halt 1 3130000 +5 halt 2 3120000 +5 halt 3 3120000 +5 halt 4 3140000 +5 halt 5 3140000 +5 halt 6 3120000 +5 halt 7 3110000 +5 halt 8 3120000 +5 halt 9 3130000 +5 halt 10 3110000 +5 halt 11 3120000 +5 halt 12 3130000 +5 halt 13 3140000 +5 halt 14 3120000 +5 halt 15 3120000 +5 halt 16 3120000 +5 halt 17 3110000 +5 halt 18 3110000 +5 halt 19 3120000 +5 halt 20 3110000 +5 halt 21 3110000 +5 halt 22 3120000 +5 halt 23 3110000 +5 halt 24 3120000 +5 halt 25 3120000 +5 halt 26 3110000 +5 halt 27 3120000 +5 halt 28 3140000 +5 halt 29 3120000 +5 halt 30 3120000 +# point pass=5 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997987 +6 halt 1 3140000 +6 halt 2 3120000 +6 halt 3 3130000 +6 halt 4 3110000 +6 halt 5 3120000 +6 halt 6 3110000 +6 halt 7 3110000 +6 halt 8 3120000 +6 halt 9 3120000 +6 halt 10 3110000 +6 halt 11 3120000 +6 halt 12 3100000 +6 halt 13 3110000 +6 halt 14 3120000 +6 halt 15 3120000 +6 halt 16 3140000 +6 halt 17 3120000 +6 halt 18 3120000 +6 halt 19 3130000 +6 halt 20 3120000 +6 halt 21 3120000 +6 halt 22 3120000 +6 halt 23 3110000 +6 halt 24 3140000 +6 halt 25 3140000 +6 halt 26 3120000 +6 halt 27 3120000 +6 halt 28 3110000 +6 halt 29 3110000 +6 halt 30 3120000 +# point pass=6 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997985 +6 run 1 3110000 +6 run 2 3110000 +6 run 3 3290000 +6 run 4 3100000 +6 run 5 3100000 +6 run 6 3110000 +6 run 7 3110000 +6 run 8 3110000 +6 run 9 3110000 +6 run 10 3110000 +6 run 11 3110000 +6 run 12 3110000 +6 run 13 3130000 +6 run 14 3110000 +6 run 15 3120000 +6 run 16 3110000 +6 run 17 3110000 +6 run 18 3110000 +6 run 19 3130000 +6 run 20 3110000 +6 run 21 3140000 +6 run 22 3130000 +6 run 23 3110000 +6 run 24 3110000 +6 run 25 3110000 +6 run 26 3110000 +6 run 27 3110000 +6 run 28 3110000 +6 run 29 3110000 +6 run 30 3120000 +# point pass=6 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997983 +7 run 1 3110000 +7 run 2 3120000 +7 run 3 3120000 +7 run 4 3120000 +7 run 5 3110000 +7 run 6 3120000 +7 run 7 3110000 +7 run 8 3130000 +7 run 9 3110000 +7 run 10 3140000 +7 run 11 3110000 +7 run 12 3120000 +7 run 13 3120000 +7 run 14 3110000 +7 run 15 3120000 +7 run 16 3120000 +7 run 17 3120000 +7 run 18 3140000 +7 run 19 3120000 +7 run 20 3110000 +7 run 21 3120000 +7 run 22 3110000 +7 run 23 3110000 +7 run 24 3130000 +7 run 25 3110000 +7 run 26 3110000 +7 run 27 3120000 +7 run 28 3110000 +7 run 29 3120000 +7 run 30 3120000 +# point pass=7 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997982 +7 halt 1 3110000 +7 halt 2 3110000 +7 halt 3 3110000 +7 halt 4 3110000 +7 halt 5 3110000 +7 halt 6 3120000 +7 halt 7 3130000 +7 halt 8 3130000 +7 halt 9 3110000 +7 halt 10 3110000 +7 halt 11 3110000 +7 halt 12 3110000 +7 halt 13 3120000 +7 halt 14 3110000 +7 halt 15 3140000 +7 halt 16 3120000 +7 halt 17 3110000 +7 halt 18 3120000 +7 halt 19 3110000 +7 halt 20 3120000 +7 halt 21 3110000 +7 halt 22 3120000 +7 halt 23 3110000 +7 halt 24 3110000 +7 halt 25 3110000 +7 halt 26 3110000 +7 halt 27 3110000 +7 halt 28 3110000 +7 halt 29 3110000 +7 halt 30 3140000 +# point pass=7 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997980 +8 run 1 3110000 +8 run 2 3100000 +8 run 3 3120000 +8 run 4 3120000 +8 run 5 3110000 +8 run 6 3100000 +8 run 7 3100000 +8 run 8 3100000 +8 run 9 3100000 +8 run 10 3110000 +8 run 11 3110000 +8 run 12 3110000 +8 run 13 3100000 +8 run 14 3100000 +8 run 15 3110000 +8 run 16 3110000 +8 run 17 3100000 +8 run 18 3110000 +8 run 19 3110000 +8 run 20 3100000 +8 run 21 3100000 +8 run 22 3110000 +8 run 23 3100000 +8 run 24 3110000 +8 run 25 3100000 +8 run 26 3110000 +8 run 27 3110000 +8 run 28 3110000 +8 run 29 3110000 +8 run 30 3110000 +# point pass=8 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997980 +8 halt 1 3100000 +8 halt 2 3110000 +8 halt 3 3110000 +8 halt 4 3110000 +8 halt 5 3120000 +8 halt 6 3110000 +8 halt 7 3100000 +8 halt 8 3120000 +8 halt 9 3120000 +8 halt 10 3110000 +8 halt 11 3110000 +8 halt 12 3120000 +8 halt 13 3110000 +8 halt 14 3110000 +8 halt 15 3130000 +8 halt 16 3120000 +8 halt 17 3110000 +8 halt 18 3100000 +8 halt 19 3110000 +8 halt 20 3120000 +8 halt 21 3150000 +8 halt 22 3110000 +8 halt 23 3140000 +8 halt 24 3130000 +8 halt 25 3120000 +8 halt 26 3110000 +8 halt 27 3110000 +8 halt 28 3110000 +8 halt 29 3110000 +8 halt 30 3110000 +# point pass=8 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997978 +# done SystemTime { tv_sec: 1793910950, tv_nsec: 445594110 } +# passes used 8 of 8 (dropped: []) +# run - halt = -1.2 +/- 2.3 mW (per-pass sd 6.4); 1/8 passes positive; 4 sample excursions beyond 6 MAD +# per-pass differences (mW): 0.0 0.0 0.0 0.0 -10.0 -10.0 10.0 0.0 +# board power while running 3111 mW; jobs/s 0.0; updates/s 0.0; measured PL clock 99.998 MHz +# pre-registered prediction 0.0 mW: measured/predicted = -inf, -0.6 sigma from it diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/solve-assign.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/solve-assign.dat new file mode 100644 index 000000000..fd1bf1dee --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/solve-assign.dat @@ -0,0 +1,505 @@ +# class solve-assign +# vqpu-edge meter passes=8 settle=60s samples=30 null=false sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793916714, tv_nsec: 341051123 } +# pass state sample power_uW +1 halt 1 3140000 +1 halt 2 3110000 +1 halt 3 3130000 +1 halt 4 3110000 +1 halt 5 3130000 +1 halt 6 3120000 +1 halt 7 3110000 +1 halt 8 3120000 +1 halt 9 3140000 +1 halt 10 3130000 +1 halt 11 3110000 +1 halt 12 3120000 +1 halt 13 3110000 +1 halt 14 3120000 +1 halt 15 3130000 +1 halt 16 3110000 +1 halt 17 3120000 +1 halt 18 3140000 +1 halt 19 3120000 +1 halt 20 3120000 +1 halt 21 3120000 +1 halt 22 3110000 +1 halt 23 3120000 +1 halt 24 3110000 +1 halt 25 3110000 +1 halt 26 3110000 +1 halt 27 3110000 +1 halt 28 3110000 +1 halt 29 3110000 +1 halt 30 3120000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998005 +1 run 1 3110000 +1 run 2 3110000 +1 run 3 3110000 +1 run 4 3110000 +1 run 5 3130000 +1 run 6 3140000 +1 run 7 3120000 +1 run 8 3100000 +1 run 9 3110000 +1 run 10 3110000 +1 run 11 3110000 +1 run 12 3110000 +1 run 13 3120000 +1 run 14 3100000 +1 run 15 3110000 +1 run 16 3110000 +1 run 17 3110000 +1 run 18 3110000 +1 run 19 3110000 +1 run 20 3110000 +1 run 21 3130000 +1 run 22 3110000 +1 run 23 3110000 +1 run 24 3110000 +1 run 25 3110000 +1 run 26 3110000 +1 run 27 3120000 +1 run 28 3110000 +1 run 29 3130000 +1 run 30 3130000 +# point pass=1 state=run jobs_per_s=94695.1 updates_per_s=33237978.4 clock_hz=99998002 +2 halt 1 3100000 +2 halt 2 3100000 +2 halt 3 3120000 +2 halt 4 3100000 +2 halt 5 3100000 +2 halt 6 3110000 +2 halt 7 3100000 +2 halt 8 3110000 +2 halt 9 3110000 +2 halt 10 3110000 +2 halt 11 3110000 +2 halt 12 3110000 +2 halt 13 3110000 +2 halt 14 3100000 +2 halt 15 3100000 +2 halt 16 3110000 +2 halt 17 3100000 +2 halt 18 3120000 +2 halt 19 3100000 +2 halt 20 3110000 +2 halt 21 3120000 +2 halt 22 3120000 +2 halt 23 3110000 +2 halt 24 3110000 +2 halt 25 3110000 +2 halt 26 3130000 +2 halt 27 3310000 +2 halt 28 3110000 +2 halt 29 3110000 +2 halt 30 3110000 +# point pass=2 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998000 +2 run 1 3120000 +2 run 2 3120000 +2 run 3 3110000 +2 run 4 3110000 +2 run 5 3110000 +2 run 6 3120000 +2 run 7 3120000 +2 run 8 3110000 +2 run 9 3120000 +2 run 10 3110000 +2 run 11 3110000 +2 run 12 3110000 +2 run 13 3110000 +2 run 14 3110000 +2 run 15 3140000 +2 run 16 3120000 +2 run 17 3120000 +2 run 18 3110000 +2 run 19 3130000 +2 run 20 3110000 +2 run 21 3130000 +2 run 22 3110000 +2 run 23 3140000 +2 run 24 3110000 +2 run 25 3120000 +2 run 26 3110000 +2 run 27 3120000 +2 run 28 3120000 +2 run 29 3110000 +2 run 30 3110000 +# point pass=2 state=run jobs_per_s=94695.1 updates_per_s=33237977.4 clock_hz=99997999 +3 run 1 3110000 +3 run 2 3120000 +3 run 3 3130000 +3 run 4 3120000 +3 run 5 3110000 +3 run 6 3120000 +3 run 7 3120000 +3 run 8 3110000 +3 run 9 3120000 +3 run 10 3110000 +3 run 11 3140000 +3 run 12 3140000 +3 run 13 3110000 +3 run 14 3120000 +3 run 15 3120000 +3 run 16 3120000 +3 run 17 3110000 +3 run 18 3130000 +3 run 19 3120000 +3 run 20 3130000 +3 run 21 3120000 +3 run 22 3110000 +3 run 23 3130000 +3 run 24 3110000 +3 run 25 3110000 +3 run 26 3120000 +3 run 27 3110000 +3 run 28 3120000 +3 run 29 3120000 +3 run 30 3120000 +# point pass=3 state=run jobs_per_s=94695.1 updates_per_s=33237977.1 clock_hz=99997998 +3 halt 1 3110000 +3 halt 2 3110000 +3 halt 3 3100000 +3 halt 4 3100000 +3 halt 5 3100000 +3 halt 6 3100000 +3 halt 7 3100000 +3 halt 8 3120000 +3 halt 9 3120000 +3 halt 10 3100000 +3 halt 11 3100000 +3 halt 12 3100000 +3 halt 13 3100000 +3 halt 14 3100000 +3 halt 15 3100000 +3 halt 16 3110000 +3 halt 17 3100000 +3 halt 18 3100000 +3 halt 19 3100000 +3 halt 20 3100000 +3 halt 21 3100000 +3 halt 22 3100000 +3 halt 23 3110000 +3 halt 24 3100000 +3 halt 25 3110000 +3 halt 26 3110000 +3 halt 27 3110000 +3 halt 28 3090000 +3 halt 29 3110000 +3 halt 30 3100000 +# point pass=3 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997997 +4 run 1 3110000 +4 run 2 3110000 +4 run 3 3140000 +4 run 4 3120000 +4 run 5 3120000 +4 run 6 3110000 +4 run 7 3110000 +4 run 8 3530000 +4 run 9 3110000 +4 run 10 3100000 +4 run 11 3110000 +4 run 12 3110000 +4 run 13 3100000 +4 run 14 3110000 +4 run 15 3100000 +4 run 16 3100000 +4 run 17 3110000 +4 run 18 3110000 +4 run 19 3100000 +4 run 20 3130000 +4 run 21 3110000 +4 run 22 3110000 +4 run 23 3110000 +4 run 24 3110000 +4 run 25 3100000 +4 run 26 3100000 +4 run 27 3110000 +4 run 28 3120000 +4 run 29 3130000 +4 run 30 3110000 +# point pass=4 state=run jobs_per_s=94695.1 updates_per_s=33237993.2 clock_hz=99998046 +4 halt 1 3110000 +4 halt 2 3110000 +4 halt 3 3100000 +4 halt 4 3110000 +4 halt 5 3110000 +4 halt 6 3090000 +4 halt 7 3100000 +4 halt 8 3100000 +4 halt 9 3110000 +4 halt 10 3100000 +4 halt 11 3110000 +4 halt 12 3110000 +4 halt 13 3100000 +4 halt 14 3110000 +4 halt 15 3100000 +4 halt 16 3100000 +4 halt 17 3120000 +4 halt 18 3090000 +4 halt 19 3100000 +4 halt 20 3100000 +4 halt 21 3100000 +4 halt 22 3100000 +4 halt 23 3100000 +4 halt 24 3100000 +4 halt 25 3110000 +4 halt 26 3110000 +4 halt 27 3100000 +4 halt 28 3100000 +4 halt 29 3110000 +4 halt 30 3110000 +# point pass=4 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998049 +5 halt 1 3170000 +5 halt 2 3110000 +5 halt 3 3120000 +5 halt 4 3100000 +5 halt 5 3130000 +5 halt 6 3120000 +5 halt 7 3120000 +5 halt 8 3100000 +5 halt 9 3120000 +5 halt 10 3120000 +5 halt 11 3120000 +5 halt 12 3120000 +5 halt 13 3130000 +5 halt 14 3140000 +5 halt 15 3120000 +5 halt 16 3110000 +5 halt 17 3120000 +5 halt 18 3110000 +5 halt 19 3110000 +5 halt 20 3110000 +5 halt 21 3110000 +5 halt 22 3130000 +5 halt 23 3110000 +5 halt 24 3110000 +5 halt 25 3120000 +5 halt 26 3110000 +5 halt 27 3120000 +5 halt 28 3110000 +5 halt 29 3120000 +5 halt 30 3120000 +# point pass=5 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998040 +5 run 1 3120000 +5 run 2 3130000 +5 run 3 3120000 +5 run 4 3130000 +5 run 5 3130000 +5 run 6 3110000 +5 run 7 3110000 +5 run 8 3120000 +5 run 9 3130000 +5 run 10 3140000 +5 run 11 3140000 +5 run 12 3120000 +5 run 13 3110000 +5 run 14 3120000 +5 run 15 3120000 +5 run 16 3120000 +5 run 17 3120000 +5 run 18 3110000 +5 run 19 3140000 +5 run 20 3120000 +5 run 21 3110000 +5 run 22 3140000 +5 run 23 3120000 +5 run 24 3120000 +5 run 25 3130000 +5 run 26 3120000 +5 run 27 3120000 +5 run 28 3130000 +5 run 29 3120000 +5 run 30 3130000 +# point pass=5 state=run jobs_per_s=94695.1 updates_per_s=33237988.9 clock_hz=99998033 +6 run 1 3120000 +6 run 2 3120000 +6 run 3 3140000 +6 run 4 3140000 +6 run 5 3120000 +6 run 6 3120000 +6 run 7 3130000 +6 run 8 3110000 +6 run 9 3110000 +6 run 10 3120000 +6 run 11 3110000 +6 run 12 3110000 +6 run 13 3110000 +6 run 14 3120000 +6 run 15 3110000 +6 run 16 3110000 +6 run 17 3110000 +6 run 18 3110000 +6 run 19 3110000 +6 run 20 3120000 +6 run 21 3120000 +6 run 22 3140000 +6 run 23 3120000 +6 run 24 3120000 +6 run 25 3120000 +6 run 26 3110000 +6 run 27 3110000 +6 run 28 3120000 +6 run 29 3110000 +6 run 30 3130000 +# point pass=6 state=run jobs_per_s=94695.1 updates_per_s=33237987.0 clock_hz=99998028 +6 halt 1 3110000 +6 halt 2 3110000 +6 halt 3 3110000 +6 halt 4 3100000 +6 halt 5 3150000 +6 halt 6 3120000 +6 halt 7 3110000 +6 halt 8 3120000 +6 halt 9 3110000 +6 halt 10 3110000 +6 halt 11 3100000 +6 halt 12 3120000 +6 halt 13 3110000 +6 halt 14 3110000 +6 halt 15 3120000 +6 halt 16 3110000 +6 halt 17 3110000 +6 halt 18 3110000 +6 halt 19 3110000 +6 halt 20 3110000 +6 halt 21 3110000 +6 halt 22 3110000 +6 halt 23 3110000 +6 halt 24 3110000 +6 halt 25 3110000 +6 halt 26 3110000 +6 halt 27 3120000 +6 halt 28 3130000 +6 halt 29 3130000 +6 halt 30 3100000 +# point pass=6 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998023 +7 halt 1 3100000 +7 halt 2 3110000 +7 halt 3 3130000 +7 halt 4 3110000 +7 halt 5 3100000 +7 halt 6 3110000 +7 halt 7 3120000 +7 halt 8 3120000 +7 halt 9 3120000 +7 halt 10 3120000 +7 halt 11 3130000 +7 halt 12 3110000 +7 halt 13 3110000 +7 halt 14 3130000 +7 halt 15 3110000 +7 halt 16 3110000 +7 halt 17 3120000 +7 halt 18 3120000 +7 halt 19 3120000 +7 halt 20 3140000 +7 halt 21 3120000 +7 halt 22 3120000 +7 halt 23 3130000 +7 halt 24 3110000 +7 halt 25 3140000 +7 halt 26 3130000 +7 halt 27 3120000 +7 halt 28 3120000 +7 halt 29 3120000 +7 halt 30 3110000 +# point pass=7 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998019 +7 run 1 3110000 +7 run 2 3120000 +7 run 3 3120000 +7 run 4 3120000 +7 run 5 3110000 +7 run 6 3120000 +7 run 7 3140000 +7 run 8 3110000 +7 run 9 3110000 +7 run 10 3120000 +7 run 11 3110000 +7 run 12 3110000 +7 run 13 3110000 +7 run 14 3110000 +7 run 15 3120000 +7 run 16 3120000 +7 run 17 3110000 +7 run 18 3120000 +7 run 19 3110000 +7 run 20 3110000 +7 run 21 3120000 +7 run 22 3130000 +7 run 23 3110000 +7 run 24 3110000 +7 run 25 3110000 +7 run 26 3110000 +7 run 27 3120000 +7 run 28 3110000 +7 run 29 3120000 +7 run 30 3140000 +# point pass=7 state=run jobs_per_s=94695.1 updates_per_s=33237983.0 clock_hz=99998016 +8 run 1 3120000 +8 run 2 3110000 +8 run 3 3120000 +8 run 4 3130000 +8 run 5 3110000 +8 run 6 3130000 +8 run 7 3130000 +8 run 8 3120000 +8 run 9 3120000 +8 run 10 3140000 +8 run 11 3120000 +8 run 12 3120000 +8 run 13 3110000 +8 run 14 3120000 +8 run 15 3110000 +8 run 16 3120000 +8 run 17 3120000 +8 run 18 3130000 +8 run 19 3140000 +8 run 20 3110000 +8 run 21 3120000 +8 run 22 3120000 +8 run 23 3110000 +8 run 24 3110000 +8 run 25 3110000 +8 run 26 3110000 +8 run 27 3140000 +8 run 28 3120000 +8 run 29 3120000 +8 run 30 3110000 +# point pass=8 state=run jobs_per_s=94695.1 updates_per_s=33237982.4 clock_hz=99998013 +8 halt 1 3090000 +8 halt 2 3110000 +8 halt 3 3110000 +8 halt 4 3100000 +8 halt 5 3110000 +8 halt 6 3110000 +8 halt 7 3120000 +8 halt 8 3110000 +8 halt 9 3110000 +8 halt 10 3110000 +8 halt 11 3110000 +8 halt 12 3110000 +8 halt 13 3110000 +8 halt 14 3100000 +8 halt 15 3120000 +8 halt 16 3120000 +8 halt 17 3110000 +8 halt 18 3110000 +8 halt 19 3110000 +8 halt 20 3110000 +8 halt 21 3120000 +8 halt 22 3110000 +8 halt 23 3110000 +8 halt 24 3120000 +8 halt 25 3100000 +8 halt 26 3110000 +8 halt 27 3100000 +8 halt 28 3100000 +8 halt 29 3100000 +8 halt 30 3110000 +# point pass=8 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998011 +# done SystemTime { tv_sec: 1793918155, tv_nsec: 266403554 } +# passes used 8 of 8 (dropped: []) +# run - halt = 3.8 +/- 3.8 mW (per-pass sd 10.6); 4/8 passes positive; 3 sample excursions beyond 6 MAD +# per-pass differences (mW): -10.0 0.0 20.0 10.0 0.0 10.0 -10.0 10.0 +# board power while running 3115 mW; jobs/s 94695.1; updates/s 33237983.4; measured PL clock 99.998 MHz +# pre-registered prediction 3.0 mW: measured/predicted = 1.25, 0.2 sigma from it diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/step.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/step.dat new file mode 100644 index 000000000..099a4918b --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/step.dat @@ -0,0 +1,610 @@ +# class t0-recall +# vqpu-edge meter passes=1 settle=0s samples=300 null=false sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793906728, tv_nsec: 609413995 } +# pass state sample power_uW +1 halt 1 3360000 +1 halt 2 3130000 +1 halt 3 3120000 +1 halt 4 3120000 +1 halt 5 3120000 +1 halt 6 3120000 +1 halt 7 3130000 +1 halt 8 3120000 +1 halt 9 3120000 +1 halt 10 3120000 +1 halt 11 3120000 +1 halt 12 3120000 +1 halt 13 3120000 +1 halt 14 3120000 +1 halt 15 3120000 +1 halt 16 3130000 +1 halt 17 3120000 +1 halt 18 3110000 +1 halt 19 3120000 +1 halt 20 3120000 +1 halt 21 3110000 +1 halt 22 3130000 +1 halt 23 3120000 +1 halt 24 3120000 +1 halt 25 3120000 +1 halt 26 3120000 +1 halt 27 3120000 +1 halt 28 3120000 +1 halt 29 3130000 +1 halt 30 3130000 +1 halt 31 3130000 +1 halt 32 3130000 +1 halt 33 3110000 +1 halt 34 3120000 +1 halt 35 3140000 +1 halt 36 3110000 +1 halt 37 3120000 +1 halt 38 3120000 +1 halt 39 3110000 +1 halt 40 3120000 +1 halt 41 3120000 +1 halt 42 3140000 +1 halt 43 3110000 +1 halt 44 3120000 +1 halt 45 3140000 +1 halt 46 3120000 +1 halt 47 3110000 +1 halt 48 3140000 +1 halt 49 3120000 +1 halt 50 3110000 +1 halt 51 3110000 +1 halt 52 3120000 +1 halt 53 3120000 +1 halt 54 3120000 +1 halt 55 3110000 +1 halt 56 3120000 +1 halt 57 3140000 +1 halt 58 3110000 +1 halt 59 3150000 +1 halt 60 3120000 +1 halt 61 3120000 +1 halt 62 3120000 +1 halt 63 3110000 +1 halt 64 3120000 +1 halt 65 3130000 +1 halt 66 3120000 +1 halt 67 3130000 +1 halt 68 3120000 +1 halt 69 3120000 +1 halt 70 3120000 +1 halt 71 3120000 +1 halt 72 3130000 +1 halt 73 3120000 +1 halt 74 3140000 +1 halt 75 3110000 +1 halt 76 3110000 +1 halt 77 3110000 +1 halt 78 3110000 +1 halt 79 3170000 +1 halt 80 3130000 +1 halt 81 3110000 +1 halt 82 3100000 +1 halt 83 3110000 +1 halt 84 3110000 +1 halt 85 3110000 +1 halt 86 3110000 +1 halt 87 3120000 +1 halt 88 3110000 +1 halt 89 3110000 +1 halt 90 3110000 +1 halt 91 3110000 +1 halt 92 3110000 +1 halt 93 3130000 +1 halt 94 3110000 +1 halt 95 3110000 +1 halt 96 3130000 +1 halt 97 3110000 +1 halt 98 3110000 +1 halt 99 3110000 +1 halt 100 3120000 +1 halt 101 3130000 +1 halt 102 3110000 +1 halt 103 3090000 +1 halt 104 3120000 +1 halt 105 3110000 +1 halt 106 3490000 +1 halt 107 3120000 +1 halt 108 3110000 +1 halt 109 3110000 +1 halt 110 3100000 +1 halt 111 3110000 +1 halt 112 3110000 +1 halt 113 3120000 +1 halt 114 3110000 +1 halt 115 3100000 +1 halt 116 3110000 +1 halt 117 3130000 +1 halt 118 3110000 +1 halt 119 3110000 +1 halt 120 3110000 +1 halt 121 3120000 +1 halt 122 3110000 +1 halt 123 3110000 +1 halt 124 3120000 +1 halt 125 3130000 +1 halt 126 3110000 +1 halt 127 3120000 +1 halt 128 3140000 +1 halt 129 3120000 +1 halt 130 3110000 +1 halt 131 3110000 +1 halt 132 3110000 +1 halt 133 3110000 +1 halt 134 3100000 +1 halt 135 3110000 +1 halt 136 3120000 +1 halt 137 3120000 +1 halt 138 3150000 +1 halt 139 3110000 +1 halt 140 3120000 +1 halt 141 3120000 +1 halt 142 3110000 +1 halt 143 3130000 +1 halt 144 3110000 +1 halt 145 3140000 +1 halt 146 3110000 +1 halt 147 3120000 +1 halt 148 3120000 +1 halt 149 3110000 +1 halt 150 3120000 +1 halt 151 3120000 +1 halt 152 3130000 +1 halt 153 3120000 +1 halt 154 3120000 +1 halt 155 3140000 +1 halt 156 3120000 +1 halt 157 3120000 +1 halt 158 3120000 +1 halt 159 3120000 +1 halt 160 3120000 +1 halt 161 3120000 +1 halt 162 3130000 +1 halt 163 3130000 +1 halt 164 3130000 +1 halt 165 3110000 +1 halt 166 3110000 +1 halt 167 3120000 +1 halt 168 3120000 +1 halt 169 3120000 +1 halt 170 3120000 +1 halt 171 3120000 +1 halt 172 3150000 +1 halt 173 3110000 +1 halt 174 3120000 +1 halt 175 3120000 +1 halt 176 3120000 +1 halt 177 3120000 +1 halt 178 3120000 +1 halt 179 3140000 +1 halt 180 3120000 +1 halt 181 3120000 +1 halt 182 3110000 +1 halt 183 3140000 +1 halt 184 3110000 +1 halt 185 3120000 +1 halt 186 3120000 +1 halt 187 3120000 +1 halt 188 3120000 +1 halt 189 3120000 +1 halt 190 3130000 +1 halt 191 3120000 +1 halt 192 3120000 +1 halt 193 3110000 +1 halt 194 3120000 +1 halt 195 3120000 +1 halt 196 3120000 +1 halt 197 3110000 +1 halt 198 3110000 +1 halt 199 3110000 +1 halt 200 3130000 +1 halt 201 3130000 +1 halt 202 3110000 +1 halt 203 3110000 +1 halt 204 3120000 +1 halt 205 3110000 +1 halt 206 3110000 +1 halt 207 3130000 +1 halt 208 3110000 +1 halt 209 3110000 +1 halt 210 3110000 +1 halt 211 3110000 +1 halt 212 3110000 +1 halt 213 3120000 +1 halt 214 3110000 +1 halt 215 3110000 +1 halt 216 3120000 +1 halt 217 3120000 +1 halt 218 3110000 +1 halt 219 3110000 +1 halt 220 3110000 +1 halt 221 3110000 +1 halt 222 3110000 +1 halt 223 3110000 +1 halt 224 3110000 +1 halt 225 3110000 +1 halt 226 3110000 +1 halt 227 3110000 +1 halt 228 3110000 +1 halt 229 3110000 +1 halt 230 3120000 +1 halt 231 3120000 +1 halt 232 3110000 +1 halt 233 3110000 +1 halt 234 3100000 +1 halt 235 3110000 +1 halt 236 3110000 +1 halt 237 3100000 +1 halt 238 3100000 +1 halt 239 3120000 +1 halt 240 3120000 +1 halt 241 3110000 +1 halt 242 3110000 +1 halt 243 3110000 +1 halt 244 3110000 +1 halt 245 3100000 +1 halt 246 3100000 +1 halt 247 3110000 +1 halt 248 3120000 +1 halt 249 3100000 +1 halt 250 3110000 +1 halt 251 3110000 +1 halt 252 3100000 +1 halt 253 3110000 +1 halt 254 3110000 +1 halt 255 3110000 +1 halt 256 3110000 +1 halt 257 3100000 +1 halt 258 3110000 +1 halt 259 3110000 +1 halt 260 3110000 +1 halt 261 3120000 +1 halt 262 3120000 +1 halt 263 3140000 +1 halt 264 3120000 +1 halt 265 3120000 +1 halt 266 3100000 +1 halt 267 3120000 +1 halt 268 3110000 +1 halt 269 3120000 +1 halt 270 3110000 +1 halt 271 3140000 +1 halt 272 3140000 +1 halt 273 3120000 +1 halt 274 3110000 +1 halt 275 3120000 +1 halt 276 3120000 +1 halt 277 3110000 +1 halt 278 3110000 +1 halt 279 3110000 +1 halt 280 3140000 +1 halt 281 3110000 +1 halt 282 3110000 +1 halt 283 3110000 +1 halt 284 3120000 +1 halt 285 3110000 +1 halt 286 3110000 +1 halt 287 3120000 +1 halt 288 3120000 +1 halt 289 3110000 +1 halt 290 3110000 +1 halt 291 3110000 +1 halt 292 3120000 +1 halt 293 3120000 +1 halt 294 3110000 +1 halt 295 3120000 +1 halt 296 3120000 +1 halt 297 3110000 +1 halt 298 3110000 +1 halt 299 3130000 +1 halt 300 3120000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997939 +1 run 1 3490000 +1 run 2 3260000 +1 run 3 3260000 +1 run 4 3260000 +1 run 5 3280000 +1 run 6 3270000 +1 run 7 3260000 +1 run 8 3260000 +1 run 9 3270000 +1 run 10 3260000 +1 run 11 3260000 +1 run 12 3260000 +1 run 13 3260000 +1 run 14 3270000 +1 run 15 3290000 +1 run 16 3260000 +1 run 17 3260000 +1 run 18 3260000 +1 run 19 3260000 +1 run 20 3280000 +1 run 21 3260000 +1 run 22 3260000 +1 run 23 3270000 +1 run 24 3250000 +1 run 25 3250000 +1 run 26 3250000 +1 run 27 3250000 +1 run 28 3260000 +1 run 29 3280000 +1 run 30 3260000 +1 run 31 3250000 +1 run 32 3260000 +1 run 33 3250000 +1 run 34 3260000 +1 run 35 3250000 +1 run 36 3260000 +1 run 37 3250000 +1 run 38 3250000 +1 run 39 3250000 +1 run 40 3250000 +1 run 41 3250000 +1 run 42 3250000 +1 run 43 3250000 +1 run 44 3260000 +1 run 45 3250000 +1 run 46 3250000 +1 run 47 3250000 +1 run 48 3260000 +1 run 49 3250000 +1 run 50 3250000 +1 run 51 3250000 +1 run 52 3280000 +1 run 53 3260000 +1 run 54 3260000 +1 run 55 3260000 +1 run 56 3260000 +1 run 57 3260000 +1 run 58 3250000 +1 run 59 3260000 +1 run 60 3250000 +1 run 61 3260000 +1 run 62 3250000 +1 run 63 3260000 +1 run 64 3310000 +1 run 65 3260000 +1 run 66 3250000 +1 run 67 3250000 +1 run 68 3260000 +1 run 69 3250000 +1 run 70 3250000 +1 run 71 3250000 +1 run 72 3250000 +1 run 73 3250000 +1 run 74 3250000 +1 run 75 3250000 +1 run 76 3270000 +1 run 77 3250000 +1 run 78 3250000 +1 run 79 3250000 +1 run 80 3250000 +1 run 81 3250000 +1 run 82 3260000 +1 run 83 3280000 +1 run 84 3280000 +1 run 85 3280000 +1 run 86 3280000 +1 run 87 3270000 +1 run 88 3260000 +1 run 89 3260000 +1 run 90 3250000 +1 run 91 3270000 +1 run 92 3260000 +1 run 93 3280000 +1 run 94 3280000 +1 run 95 3260000 +1 run 96 3260000 +1 run 97 3270000 +1 run 98 3260000 +1 run 99 3270000 +1 run 100 3260000 +1 run 101 3260000 +1 run 102 3270000 +1 run 103 3260000 +1 run 104 3260000 +1 run 105 3250000 +1 run 106 3260000 +1 run 107 3260000 +1 run 108 3280000 +1 run 109 3260000 +1 run 110 3260000 +1 run 111 3260000 +1 run 112 3270000 +1 run 113 3280000 +1 run 114 3270000 +1 run 115 3260000 +1 run 116 3290000 +1 run 117 3280000 +1 run 118 3260000 +1 run 119 3260000 +1 run 120 3270000 +1 run 121 3260000 +1 run 122 3270000 +1 run 123 3260000 +1 run 124 3260000 +1 run 125 3270000 +1 run 126 3250000 +1 run 127 3500000 +1 run 128 3260000 +1 run 129 3260000 +1 run 130 3260000 +1 run 131 3270000 +1 run 132 3460000 +1 run 133 3260000 +1 run 134 3260000 +1 run 135 3260000 +1 run 136 3280000 +1 run 137 3260000 +1 run 138 3260000 +1 run 139 3280000 +1 run 140 3280000 +1 run 141 3260000 +1 run 142 3260000 +1 run 143 3260000 +1 run 144 3270000 +1 run 145 3270000 +1 run 146 3260000 +1 run 147 3250000 +1 run 148 3280000 +1 run 149 3260000 +1 run 150 3260000 +1 run 151 3260000 +1 run 152 3260000 +1 run 153 3260000 +1 run 154 3250000 +1 run 155 3260000 +1 run 156 3260000 +1 run 157 3260000 +1 run 158 3260000 +1 run 159 3260000 +1 run 160 3260000 +1 run 161 3260000 +1 run 162 3260000 +1 run 163 3280000 +1 run 164 3260000 +1 run 165 3270000 +1 run 166 3260000 +1 run 167 3260000 +1 run 168 3260000 +1 run 169 3260000 +1 run 170 3260000 +1 run 171 3290000 +1 run 172 3290000 +1 run 173 3260000 +1 run 174 3270000 +1 run 175 3260000 +1 run 176 3260000 +1 run 177 3260000 +1 run 178 3270000 +1 run 179 3260000 +1 run 180 3280000 +1 run 181 3260000 +1 run 182 3260000 +1 run 183 3260000 +1 run 184 3270000 +1 run 185 3260000 +1 run 186 3260000 +1 run 187 3260000 +1 run 188 3270000 +1 run 189 3260000 +1 run 190 3260000 +1 run 191 3260000 +1 run 192 3260000 +1 run 193 3260000 +1 run 194 3250000 +1 run 195 3280000 +1 run 196 3260000 +1 run 197 3260000 +1 run 198 3260000 +1 run 199 3270000 +1 run 200 3260000 +1 run 201 3260000 +1 run 202 3260000 +1 run 203 3290000 +1 run 204 3280000 +1 run 205 3260000 +1 run 206 3250000 +1 run 207 3280000 +1 run 208 3260000 +1 run 209 3260000 +1 run 210 3260000 +1 run 211 3260000 +1 run 212 3260000 +1 run 213 3260000 +1 run 214 3270000 +1 run 215 3270000 +1 run 216 3250000 +1 run 217 3250000 +1 run 218 3280000 +1 run 219 3260000 +1 run 220 3280000 +1 run 221 3250000 +1 run 222 3250000 +1 run 223 3260000 +1 run 224 3270000 +1 run 225 3260000 +1 run 226 3270000 +1 run 227 3280000 +1 run 228 3260000 +1 run 229 3260000 +1 run 230 3260000 +1 run 231 3260000 +1 run 232 3270000 +1 run 233 3260000 +1 run 234 3260000 +1 run 235 3280000 +1 run 236 3260000 +1 run 237 3260000 +1 run 238 3260000 +1 run 239 3260000 +1 run 240 3260000 +1 run 241 3260000 +1 run 242 3260000 +1 run 243 3250000 +1 run 244 3260000 +1 run 245 3260000 +1 run 246 3260000 +1 run 247 3260000 +1 run 248 3260000 +1 run 249 3280000 +1 run 250 3270000 +1 run 251 3260000 +1 run 252 3260000 +1 run 253 3260000 +1 run 254 3260000 +1 run 255 3260000 +1 run 256 3260000 +1 run 257 3270000 +1 run 258 3280000 +1 run 259 3290000 +1 run 260 3260000 +1 run 261 3260000 +1 run 262 3260000 +1 run 263 3260000 +1 run 264 3260000 +1 run 265 3260000 +1 run 266 3260000 +1 run 267 3270000 +1 run 268 3260000 +1 run 269 3260000 +1 run 270 3260000 +1 run 271 3260000 +1 run 272 3250000 +1 run 273 3260000 +1 run 274 3270000 +1 run 275 3260000 +1 run 276 3260000 +1 run 277 3260000 +1 run 278 3260000 +1 run 279 3260000 +1 run 280 3290000 +1 run 281 3250000 +1 run 282 3280000 +1 run 283 3260000 +1 run 284 3270000 +1 run 285 3260000 +1 run 286 3250000 +1 run 287 3260000 +1 run 288 3260000 +1 run 289 3260000 +1 run 290 3280000 +1 run 291 3260000 +1 run 292 3250000 +1 run 293 3260000 +1 run 294 3270000 +1 run 295 3250000 +1 run 296 3260000 +1 run 297 3260000 +1 run 298 3260000 +1 run 299 3260000 +1 run 300 3260000 +# point pass=1 state=run jobs_per_s=12499749.7 updates_per_s=0.0 clock_hz=99997996 +# done SystemTime { tv_sec: 1793907328, tv_nsec: 966768242 } +# passes used 1 of 1 (dropped: []) +# run - halt = 140.0 +/- NaN mW (per-pass sd NaN); 1/1 passes positive; 6 sample excursions beyond 6 MAD +# per-pass differences (mW): 140.0 +# board power while running 3260 mW; jobs/s 12499749.7; updates/s 0.0; measured PL clock 99.998 MHz diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/t0-idle.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/t0-idle.dat new file mode 100644 index 000000000..dd89dc6cb --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/t0-idle.dat @@ -0,0 +1,505 @@ +# class t0-idle +# vqpu-edge meter passes=8 settle=60s samples=30 null=false sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793915273, tv_nsec: 378650480 } +# pass state sample power_uW +1 run 1 3110000 +1 run 2 3110000 +1 run 3 3120000 +1 run 4 3120000 +1 run 5 3110000 +1 run 6 3110000 +1 run 7 3130000 +1 run 8 3120000 +1 run 9 3110000 +1 run 10 3110000 +1 run 11 3110000 +1 run 12 3110000 +1 run 13 3110000 +1 run 14 3170000 +1 run 15 3120000 +1 run 16 3110000 +1 run 17 3110000 +1 run 18 3120000 +1 run 19 3120000 +1 run 20 3180000 +1 run 21 3130000 +1 run 22 3130000 +1 run 23 3120000 +1 run 24 3110000 +1 run 25 3110000 +1 run 26 3110000 +1 run 27 3110000 +1 run 28 3100000 +1 run 29 3110000 +1 run 30 3120000 +# point pass=1 state=run jobs_per_s=33332741.4 updates_per_s=0.0 clock_hz=99998201 +1 halt 1 3140000 +1 halt 2 3110000 +1 halt 3 3110000 +1 halt 4 3110000 +1 halt 5 3130000 +1 halt 6 3110000 +1 halt 7 3110000 +1 halt 8 3110000 +1 halt 9 3110000 +1 halt 10 3110000 +1 halt 11 3110000 +1 halt 12 3110000 +1 halt 13 3130000 +1 halt 14 3110000 +1 halt 15 3110000 +1 halt 16 3110000 +1 halt 17 3110000 +1 halt 18 3120000 +1 halt 19 3120000 +1 halt 20 3120000 +1 halt 21 3110000 +1 halt 22 3110000 +1 halt 23 3110000 +1 halt 24 3100000 +1 halt 25 3120000 +1 halt 26 3110000 +1 halt 27 3140000 +1 halt 28 3110000 +1 halt 29 3120000 +1 halt 30 3120000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998166 +2 run 1 3120000 +2 run 2 3130000 +2 run 3 3120000 +2 run 4 3130000 +2 run 5 3130000 +2 run 6 3120000 +2 run 7 3140000 +2 run 8 3120000 +2 run 9 3120000 +2 run 10 3120000 +2 run 11 3130000 +2 run 12 3120000 +2 run 13 3140000 +2 run 14 3150000 +2 run 15 3130000 +2 run 16 3140000 +2 run 17 3120000 +2 run 18 3110000 +2 run 19 3120000 +2 run 20 3110000 +2 run 21 3120000 +2 run 22 3120000 +2 run 23 3110000 +2 run 24 3140000 +2 run 25 3120000 +2 run 26 3120000 +2 run 27 3120000 +2 run 28 3110000 +2 run 29 3120000 +2 run 30 3110000 +# point pass=2 state=run jobs_per_s=33332720.0 updates_per_s=0.0 clock_hz=99998138 +2 halt 1 3110000 +2 halt 2 3110000 +2 halt 3 3110000 +2 halt 4 3140000 +2 halt 5 3100000 +2 halt 6 3110000 +2 halt 7 3120000 +2 halt 8 3110000 +2 halt 9 3120000 +2 halt 10 3120000 +2 halt 11 3110000 +2 halt 12 3120000 +2 halt 13 3120000 +2 halt 14 3110000 +2 halt 15 3110000 +2 halt 16 3100000 +2 halt 17 3110000 +2 halt 18 3110000 +2 halt 19 3100000 +2 halt 20 3110000 +2 halt 21 3110000 +2 halt 22 3110000 +2 halt 23 3270000 +2 halt 24 3110000 +2 halt 25 3110000 +2 halt 26 3100000 +2 halt 27 3120000 +2 halt 28 3460000 +2 halt 29 3110000 +2 halt 30 3110000 +# point pass=2 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998114 +3 halt 1 3120000 +3 halt 2 3110000 +3 halt 3 3110000 +3 halt 4 3100000 +3 halt 5 3110000 +3 halt 6 3110000 +3 halt 7 3100000 +3 halt 8 3100000 +3 halt 9 3150000 +3 halt 10 3110000 +3 halt 11 3100000 +3 halt 12 3100000 +3 halt 13 3110000 +3 halt 14 3100000 +3 halt 15 3110000 +3 halt 16 3110000 +3 halt 17 3110000 +3 halt 18 3100000 +3 halt 19 3110000 +3 halt 20 3120000 +3 halt 21 3120000 +3 halt 22 3120000 +3 halt 23 3110000 +3 halt 24 3150000 +3 halt 25 3110000 +3 halt 26 3120000 +3 halt 27 3120000 +3 halt 28 3110000 +3 halt 29 3110000 +3 halt 30 3120000 +# point pass=3 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998094 +3 run 1 3110000 +3 run 2 3120000 +3 run 3 3120000 +3 run 4 3110000 +3 run 5 3120000 +3 run 6 3140000 +3 run 7 3110000 +3 run 8 3110000 +3 run 9 3120000 +3 run 10 3110000 +3 run 11 3120000 +3 run 12 3120000 +3 run 13 3120000 +3 run 14 3120000 +3 run 15 3110000 +3 run 16 3120000 +3 run 17 3120000 +3 run 18 3120000 +3 run 19 3120000 +3 run 20 3120000 +3 run 21 3140000 +3 run 22 3110000 +3 run 23 3110000 +3 run 24 3120000 +3 run 25 3110000 +3 run 26 3120000 +3 run 27 3120000 +3 run 28 3110000 +3 run 29 3140000 +3 run 30 3140000 +# point pass=3 state=run jobs_per_s=33332700.4 updates_per_s=0.0 clock_hz=99998079 +4 run 1 3110000 +4 run 2 3110000 +4 run 3 3130000 +4 run 4 3110000 +4 run 5 3120000 +4 run 6 3110000 +4 run 7 3110000 +4 run 8 3110000 +4 run 9 3110000 +4 run 10 3110000 +4 run 11 3120000 +4 run 12 3110000 +4 run 13 3130000 +4 run 14 3120000 +4 run 15 3120000 +4 run 16 3110000 +4 run 17 3130000 +4 run 18 3130000 +4 run 19 3110000 +4 run 20 3110000 +4 run 21 3120000 +4 run 22 3100000 +4 run 23 3120000 +4 run 24 3120000 +4 run 25 3120000 +4 run 26 3130000 +4 run 27 3110000 +4 run 28 3110000 +4 run 29 3120000 +4 run 30 3110000 +# point pass=4 state=run jobs_per_s=33332695.3 updates_per_s=0.0 clock_hz=99998064 +4 halt 1 3100000 +4 halt 2 3110000 +4 halt 3 3100000 +4 halt 4 3110000 +4 halt 5 3110000 +4 halt 6 3120000 +4 halt 7 3110000 +4 halt 8 3110000 +4 halt 9 3100000 +4 halt 10 3110000 +4 halt 11 3100000 +4 halt 12 3110000 +4 halt 13 3100000 +4 halt 14 3120000 +4 halt 15 3110000 +4 halt 16 3110000 +4 halt 17 3110000 +4 halt 18 3110000 +4 halt 19 3110000 +4 halt 20 3100000 +4 halt 21 3110000 +4 halt 22 3110000 +4 halt 23 3120000 +4 halt 24 3100000 +4 halt 25 3110000 +4 halt 26 3110000 +4 halt 27 3090000 +4 halt 28 3100000 +4 halt 29 3090000 +4 halt 30 3110000 +# point pass=4 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998052 +5 run 1 3140000 +5 run 2 3120000 +5 run 3 3110000 +5 run 4 3110000 +5 run 5 3120000 +5 run 6 3140000 +5 run 7 3120000 +5 run 8 3110000 +5 run 9 3110000 +5 run 10 3110000 +5 run 11 3110000 +5 run 12 3110000 +5 run 13 3120000 +5 run 14 3120000 +5 run 15 3130000 +5 run 16 3120000 +5 run 17 3110000 +5 run 18 3120000 +5 run 19 3120000 +5 run 20 3110000 +5 run 21 3110000 +5 run 22 3110000 +5 run 23 3130000 +5 run 24 3110000 +5 run 25 3110000 +5 run 26 3120000 +5 run 27 3120000 +5 run 28 3120000 +5 run 29 3110000 +5 run 30 3110000 +# point pass=5 state=run jobs_per_s=33332688.2 updates_per_s=0.0 clock_hz=99998042 +5 halt 1 3110000 +5 halt 2 3120000 +5 halt 3 3130000 +5 halt 4 3110000 +5 halt 5 3110000 +5 halt 6 3100000 +5 halt 7 3100000 +5 halt 8 3110000 +5 halt 9 3110000 +5 halt 10 3120000 +5 halt 11 3120000 +5 halt 12 3110000 +5 halt 13 3110000 +5 halt 14 3120000 +5 halt 15 3110000 +5 halt 16 3100000 +5 halt 17 3110000 +5 halt 18 3120000 +5 halt 19 3110000 +5 halt 20 3100000 +5 halt 21 3110000 +5 halt 22 3110000 +5 halt 23 3110000 +5 halt 24 3110000 +5 halt 25 3110000 +5 halt 26 3120000 +5 halt 27 3110000 +5 halt 28 3110000 +5 halt 29 3110000 +5 halt 30 3110000 +# point pass=5 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998034 +6 halt 1 3120000 +6 halt 2 3110000 +6 halt 3 3110000 +6 halt 4 3110000 +6 halt 5 3110000 +6 halt 6 3110000 +6 halt 7 3120000 +6 halt 8 3140000 +6 halt 9 3110000 +6 halt 10 3120000 +6 halt 11 3110000 +6 halt 12 3110000 +6 halt 13 3110000 +6 halt 14 3110000 +6 halt 15 3110000 +6 halt 16 3120000 +6 halt 17 3120000 +6 halt 18 3110000 +6 halt 19 3110000 +6 halt 20 3120000 +6 halt 21 3120000 +6 halt 22 3100000 +6 halt 23 3130000 +6 halt 24 3110000 +6 halt 25 3110000 +6 halt 26 3110000 +6 halt 27 3110000 +6 halt 28 3110000 +6 halt 29 3110000 +6 halt 30 3110000 +# point pass=6 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998028 +6 run 1 3110000 +6 run 2 3110000 +6 run 3 3110000 +6 run 4 3120000 +6 run 5 3130000 +6 run 6 3110000 +6 run 7 3110000 +6 run 8 3110000 +6 run 9 3110000 +6 run 10 3110000 +6 run 11 3120000 +6 run 12 3110000 +6 run 13 3120000 +6 run 14 3110000 +6 run 15 3110000 +6 run 16 3120000 +6 run 17 3120000 +6 run 18 3110000 +6 run 19 3110000 +6 run 20 3140000 +6 run 21 3120000 +6 run 22 3120000 +6 run 23 3120000 +6 run 24 3110000 +6 run 25 3120000 +6 run 26 3120000 +6 run 27 3110000 +6 run 28 3140000 +6 run 29 3120000 +6 run 30 3120000 +# point pass=6 state=run jobs_per_s=33332681.5 updates_per_s=0.0 clock_hz=99998021 +7 run 1 3110000 +7 run 2 3110000 +7 run 3 3110000 +7 run 4 3110000 +7 run 5 3110000 +7 run 6 3110000 +7 run 7 3110000 +7 run 8 3120000 +7 run 9 3110000 +7 run 10 3110000 +7 run 11 3110000 +7 run 12 3110000 +7 run 13 3110000 +7 run 14 3100000 +7 run 15 3100000 +7 run 16 3120000 +7 run 17 3120000 +7 run 18 3110000 +7 run 19 3100000 +7 run 20 3110000 +7 run 21 3110000 +7 run 22 3120000 +7 run 23 3110000 +7 run 24 3110000 +7 run 25 3140000 +7 run 26 3110000 +7 run 27 3100000 +7 run 28 3110000 +7 run 29 3110000 +7 run 30 3110000 +# point pass=7 state=run jobs_per_s=33332680.2 updates_per_s=0.0 clock_hz=99998017 +7 halt 1 3110000 +7 halt 2 3110000 +7 halt 3 3110000 +7 halt 4 3130000 +7 halt 5 3130000 +7 halt 6 3110000 +7 halt 7 3110000 +7 halt 8 3120000 +7 halt 9 3110000 +7 halt 10 3120000 +7 halt 11 3110000 +7 halt 12 3100000 +7 halt 13 3130000 +7 halt 14 3130000 +7 halt 15 3100000 +7 halt 16 3110000 +7 halt 17 3120000 +7 halt 18 3120000 +7 halt 19 3120000 +7 halt 20 3110000 +7 halt 21 3110000 +7 halt 22 3140000 +7 halt 23 3110000 +7 halt 24 3110000 +7 halt 25 3110000 +7 halt 26 3110000 +7 halt 27 3120000 +7 halt 28 3120000 +7 halt 29 3120000 +7 halt 30 3110000 +# point pass=7 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998014 +8 run 1 3110000 +8 run 2 3130000 +8 run 3 3110000 +8 run 4 3110000 +8 run 5 3110000 +8 run 6 3110000 +8 run 7 3110000 +8 run 8 3110000 +8 run 9 3110000 +8 run 10 3130000 +8 run 11 3110000 +8 run 12 3110000 +8 run 13 3110000 +8 run 14 3110000 +8 run 15 3120000 +8 run 16 3110000 +8 run 17 3110000 +8 run 18 3110000 +8 run 19 3110000 +8 run 20 3120000 +8 run 21 3110000 +8 run 22 3110000 +8 run 23 3110000 +8 run 24 3110000 +8 run 25 3130000 +8 run 26 3120000 +8 run 27 3110000 +8 run 28 3120000 +8 run 29 3110000 +8 run 30 3130000 +# point pass=8 state=run jobs_per_s=33332678.3 updates_per_s=0.0 clock_hz=99998009 +8 halt 1 3110000 +8 halt 2 3110000 +8 halt 3 3110000 +8 halt 4 3110000 +8 halt 5 3110000 +8 halt 6 3110000 +8 halt 7 3120000 +8 halt 8 3120000 +8 halt 9 3110000 +8 halt 10 3100000 +8 halt 11 3110000 +8 halt 12 3110000 +8 halt 13 3110000 +8 halt 14 3110000 +8 halt 15 3110000 +8 halt 16 3110000 +8 halt 17 3100000 +8 halt 18 3100000 +8 halt 19 3110000 +8 halt 20 3110000 +8 halt 21 3120000 +8 halt 22 3130000 +8 halt 23 3110000 +8 halt 24 3110000 +8 halt 25 3110000 +8 halt 26 3110000 +8 halt 27 3110000 +8 halt 28 3110000 +8 halt 29 3110000 +8 halt 30 3140000 +# point pass=8 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998006 +# done SystemTime { tv_sec: 1793916714, tv_nsec: 281430358 } +# passes used 8 of 8 (dropped: []) +# run - halt = 3.8 +/- 1.6 mW (per-pass sd 4.4); 4/8 passes positive; 4 sample excursions beyond 6 MAD +# per-pass differences (mW): 0.0 10.0 10.0 0.0 5.0 5.0 0.0 0.0 +# board power while running 3114 mW; jobs/s 33332698.2; updates/s 0.0; measured PL clock 99.998 MHz +# pre-registered prediction 1.0 mW: measured/predicted = 3.75, 1.8 sigma from it diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/t0-recall-4core.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/t0-recall-4core.dat new file mode 100644 index 000000000..52aed011c --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/t0-recall-4core.dat @@ -0,0 +1,754 @@ +# class t0-recall cores 4 +# vqpu-edge meter passes=12 settle=60s samples=30 null=false sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793920187, tv_nsec: 861113367 } +# pass state sample power_uW +1 halt 1 3250000 +1 halt 2 3250000 +1 halt 3 3250000 +1 halt 4 3250000 +1 halt 5 3250000 +1 halt 6 3280000 +1 halt 7 3250000 +1 halt 8 3250000 +1 halt 9 3260000 +1 halt 10 3260000 +1 halt 11 3250000 +1 halt 12 3260000 +1 halt 13 3240000 +1 halt 14 3260000 +1 halt 15 3240000 +1 halt 16 3250000 +1 halt 17 3250000 +1 halt 18 3250000 +1 halt 19 3250000 +1 halt 20 3240000 +1 halt 21 3240000 +1 halt 22 3250000 +1 halt 23 3250000 +1 halt 24 3250000 +1 halt 25 3250000 +1 halt 26 3240000 +1 halt 27 3260000 +1 halt 28 3250000 +1 halt 29 3260000 +1 halt 30 3250000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997938 +1 run 1 3840000 +1 run 2 3820000 +1 run 3 3840000 +1 run 4 3850000 +1 run 5 3830000 +1 run 6 3840000 +1 run 7 3830000 +1 run 8 4070000 +1 run 9 3830000 +1 run 10 3830000 +1 run 11 3830000 +1 run 12 3830000 +1 run 13 3840000 +1 run 14 3830000 +1 run 15 3840000 +1 run 16 3830000 +1 run 17 3840000 +1 run 18 3850000 +1 run 19 3830000 +1 run 20 3840000 +1 run 21 3830000 +1 run 22 3830000 +1 run 23 3830000 +1 run 24 3840000 +1 run 25 3840000 +1 run 26 3820000 +1 run 27 3830000 +1 run 28 3840000 +1 run 29 3850000 +1 run 30 3830000 +# point pass=1 state=run jobs_per_s=12499746.0 updates_per_s=0.0 clock_hz=99997944 +2 halt 1 3260000 +2 halt 2 3260000 +2 halt 3 3260000 +2 halt 4 3260000 +2 halt 5 3250000 +2 halt 6 3250000 +2 halt 7 3260000 +2 halt 8 3250000 +2 halt 9 3260000 +2 halt 10 3260000 +2 halt 11 3270000 +2 halt 12 3260000 +2 halt 13 3250000 +2 halt 14 3260000 +2 halt 15 3260000 +2 halt 16 3260000 +2 halt 17 3260000 +2 halt 18 3250000 +2 halt 19 3280000 +2 halt 20 3250000 +2 halt 21 3260000 +2 halt 22 3250000 +2 halt 23 3250000 +2 halt 24 3260000 +2 halt 25 3260000 +2 halt 26 3260000 +2 halt 27 3260000 +2 halt 28 3260000 +2 halt 29 3260000 +2 halt 30 3260000 +# point pass=2 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997946 +2 run 1 3840000 +2 run 2 3830000 +2 run 3 3840000 +2 run 4 3840000 +2 run 5 3850000 +2 run 6 3840000 +2 run 7 3850000 +2 run 8 3830000 +2 run 9 3840000 +2 run 10 3850000 +2 run 11 3910000 +2 run 12 3830000 +2 run 13 3840000 +2 run 14 3840000 +2 run 15 3840000 +2 run 16 3830000 +2 run 17 3850000 +2 run 18 3840000 +2 run 19 3830000 +2 run 20 3850000 +2 run 21 3840000 +2 run 22 3840000 +2 run 23 3840000 +2 run 24 3840000 +2 run 25 3850000 +2 run 26 3840000 +2 run 27 3840000 +2 run 28 3850000 +2 run 29 3840000 +2 run 30 3840000 +# point pass=2 state=run jobs_per_s=12499746.6 updates_per_s=0.0 clock_hz=99997951 +3 halt 1 3240000 +3 halt 2 3250000 +3 halt 3 3250000 +3 halt 4 3250000 +3 halt 5 3250000 +3 halt 6 3240000 +3 halt 7 3250000 +3 halt 8 3250000 +3 halt 9 3250000 +3 halt 10 3250000 +3 halt 11 3240000 +3 halt 12 3240000 +3 halt 13 3250000 +3 halt 14 3250000 +3 halt 15 3250000 +3 halt 16 3260000 +3 halt 17 3260000 +3 halt 18 3250000 +3 halt 19 3250000 +3 halt 20 3270000 +3 halt 21 3250000 +3 halt 22 3270000 +3 halt 23 3240000 +3 halt 24 3250000 +3 halt 25 3250000 +3 halt 26 3240000 +3 halt 27 3250000 +3 halt 28 3250000 +3 halt 29 3240000 +3 halt 30 3250000 +# point pass=3 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997953 +3 run 1 3850000 +3 run 2 3830000 +3 run 3 3840000 +3 run 4 3830000 +3 run 5 3850000 +3 run 6 3870000 +3 run 7 3840000 +3 run 8 3830000 +3 run 9 3840000 +3 run 10 3840000 +3 run 11 3820000 +3 run 12 3840000 +3 run 13 3840000 +3 run 14 3860000 +3 run 15 3830000 +3 run 16 3840000 +3 run 17 3830000 +3 run 18 3830000 +3 run 19 3840000 +3 run 20 3830000 +3 run 21 3830000 +3 run 22 3860000 +3 run 23 3830000 +3 run 24 3840000 +3 run 25 3840000 +3 run 26 3830000 +3 run 27 3840000 +3 run 28 3830000 +3 run 29 3840000 +3 run 30 3850000 +# point pass=3 state=run jobs_per_s=12499747.2 updates_per_s=0.0 clock_hz=99997955 +4 run 1 3830000 +4 run 2 3840000 +4 run 3 3830000 +4 run 4 3830000 +4 run 5 3830000 +4 run 6 3840000 +4 run 7 3830000 +4 run 8 3820000 +4 run 9 3820000 +4 run 10 3820000 +4 run 11 3830000 +4 run 12 3820000 +4 run 13 3830000 +4 run 14 3850000 +4 run 15 3830000 +4 run 16 3820000 +4 run 17 3840000 +4 run 18 3830000 +4 run 19 3830000 +4 run 20 3820000 +4 run 21 3830000 +4 run 22 3840000 +4 run 23 3820000 +4 run 24 3830000 +4 run 25 3840000 +4 run 26 3840000 +4 run 27 3820000 +4 run 28 3830000 +4 run 29 3820000 +4 run 30 3820000 +# point pass=4 state=run jobs_per_s=12499747.5 updates_per_s=0.0 clock_hz=99997957 +4 halt 1 3240000 +4 halt 2 3240000 +4 halt 3 3240000 +4 halt 4 3230000 +4 halt 5 3240000 +4 halt 6 3240000 +4 halt 7 3240000 +4 halt 8 3250000 +4 halt 9 3250000 +4 halt 10 3240000 +4 halt 11 3240000 +4 halt 12 3240000 +4 halt 13 3230000 +4 halt 14 3250000 +4 halt 15 3240000 +4 halt 16 3240000 +4 halt 17 3230000 +4 halt 18 3240000 +4 halt 19 3230000 +4 halt 20 3240000 +4 halt 21 3240000 +4 halt 22 3260000 +4 halt 23 3250000 +4 halt 24 3240000 +4 halt 25 3230000 +4 halt 26 3240000 +4 halt 27 3250000 +4 halt 28 3240000 +4 halt 29 3240000 +4 halt 30 3240000 +# point pass=4 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997959 +5 halt 1 3250000 +5 halt 2 3240000 +5 halt 3 3250000 +5 halt 4 3250000 +5 halt 5 3240000 +5 halt 6 3250000 +5 halt 7 3230000 +5 halt 8 3240000 +5 halt 9 3250000 +5 halt 10 3250000 +5 halt 11 3260000 +5 halt 12 3240000 +5 halt 13 3250000 +5 halt 14 3240000 +5 halt 15 3240000 +5 halt 16 3240000 +5 halt 17 3250000 +5 halt 18 3240000 +5 halt 19 3270000 +5 halt 20 3250000 +5 halt 21 3240000 +5 halt 22 3240000 +5 halt 23 3250000 +5 halt 24 3250000 +5 halt 25 3250000 +5 halt 26 3250000 +5 halt 27 3260000 +5 halt 28 3240000 +5 halt 29 3250000 +5 halt 30 3240000 +# point pass=5 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997960 +5 run 1 3830000 +5 run 2 3830000 +5 run 3 3840000 +5 run 4 3840000 +5 run 5 3830000 +5 run 6 3830000 +5 run 7 3850000 +5 run 8 3840000 +5 run 9 3820000 +5 run 10 3830000 +5 run 11 3840000 +5 run 12 3830000 +5 run 13 3840000 +5 run 14 3830000 +5 run 15 3830000 +5 run 16 3840000 +5 run 17 3830000 +5 run 18 3910000 +5 run 19 3830000 +5 run 20 3830000 +5 run 21 3830000 +5 run 22 3830000 +5 run 23 4190000 +5 run 24 3830000 +5 run 25 3830000 +5 run 26 3840000 +5 run 27 3830000 +5 run 28 3830000 +5 run 29 3830000 +5 run 30 3830000 +# point pass=5 state=run jobs_per_s=12499747.9 updates_per_s=0.0 clock_hz=99997961 +6 halt 1 3240000 +6 halt 2 3250000 +6 halt 3 3260000 +6 halt 4 3260000 +6 halt 5 3280000 +6 halt 6 3250000 +6 halt 7 3250000 +6 halt 8 3250000 +6 halt 9 3250000 +6 halt 10 3250000 +6 halt 11 3240000 +6 halt 12 3240000 +6 halt 13 3260000 +6 halt 14 3260000 +6 halt 15 3240000 +6 halt 16 3250000 +6 halt 17 3250000 +6 halt 18 3250000 +6 halt 19 3250000 +6 halt 20 3250000 +6 halt 21 3250000 +6 halt 22 3250000 +6 halt 23 3250000 +6 halt 24 3250000 +6 halt 25 3250000 +6 halt 26 3250000 +6 halt 27 3250000 +6 halt 28 3260000 +6 halt 29 3250000 +6 halt 30 3250000 +# point pass=6 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997962 +6 run 1 3840000 +6 run 2 3830000 +6 run 3 3830000 +6 run 4 3830000 +6 run 5 3830000 +6 run 6 3820000 +6 run 7 3820000 +6 run 8 3840000 +6 run 9 3820000 +6 run 10 3830000 +6 run 11 3830000 +6 run 12 3830000 +6 run 13 3830000 +6 run 14 3830000 +6 run 15 3830000 +6 run 16 3840000 +6 run 17 3820000 +6 run 18 3830000 +6 run 19 3840000 +6 run 20 3830000 +6 run 21 3830000 +6 run 22 3830000 +6 run 23 3830000 +6 run 24 3850000 +6 run 25 3850000 +6 run 26 3840000 +6 run 27 3830000 +6 run 28 3830000 +6 run 29 3820000 +6 run 30 3840000 +# point pass=6 state=run jobs_per_s=12499748.0 updates_per_s=0.0 clock_hz=99997963 +7 run 1 3830000 +7 run 2 3830000 +7 run 3 3830000 +7 run 4 3830000 +7 run 5 3840000 +7 run 6 3830000 +7 run 7 3830000 +7 run 8 3830000 +7 run 9 3820000 +7 run 10 3830000 +7 run 11 3830000 +7 run 12 3830000 +7 run 13 3850000 +7 run 14 3830000 +7 run 15 3840000 +7 run 16 3830000 +7 run 17 3840000 +7 run 18 3830000 +7 run 19 3830000 +7 run 20 3830000 +7 run 21 3850000 +7 run 22 3850000 +7 run 23 3830000 +7 run 24 3830000 +7 run 25 3840000 +7 run 26 3840000 +7 run 27 3830000 +7 run 28 3830000 +7 run 29 3830000 +7 run 30 3840000 +# point pass=7 state=run jobs_per_s=12499748.1 updates_per_s=0.0 clock_hz=99997963 +7 halt 1 3250000 +7 halt 2 3250000 +7 halt 3 3240000 +7 halt 4 3240000 +7 halt 5 3250000 +7 halt 6 3240000 +7 halt 7 3240000 +7 halt 8 3240000 +7 halt 9 3250000 +7 halt 10 3270000 +7 halt 11 3250000 +7 halt 12 3250000 +7 halt 13 3240000 +7 halt 14 3240000 +7 halt 15 3240000 +7 halt 16 3240000 +7 halt 17 3250000 +7 halt 18 3260000 +7 halt 19 3250000 +7 halt 20 3240000 +7 halt 21 3240000 +7 halt 22 3250000 +7 halt 23 3250000 +7 halt 24 3240000 +7 halt 25 3250000 +7 halt 26 3250000 +7 halt 27 3240000 +7 halt 28 3240000 +7 halt 29 3240000 +7 halt 30 3250000 +# point pass=7 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998258 +8 run 1 3840000 +8 run 2 3850000 +8 run 3 3830000 +8 run 4 3840000 +8 run 5 3840000 +8 run 6 3860000 +8 run 7 3850000 +8 run 8 3840000 +8 run 9 3840000 +8 run 10 3850000 +8 run 11 3840000 +8 run 12 3840000 +8 run 13 3840000 +8 run 14 3840000 +8 run 15 3870000 +8 run 16 3840000 +8 run 17 3820000 +8 run 18 3840000 +8 run 19 3840000 +8 run 20 3840000 +8 run 21 3840000 +8 run 22 3850000 +8 run 23 3840000 +8 run 24 3830000 +8 run 25 3840000 +8 run 26 3850000 +8 run 27 3840000 +8 run 28 3840000 +8 run 29 3840000 +8 run 30 3860000 +# point pass=8 state=run jobs_per_s=12499779.7 updates_per_s=0.0 clock_hz=99998216 +8 halt 1 3240000 +8 halt 2 3260000 +8 halt 3 3260000 +8 halt 4 3260000 +8 halt 5 3240000 +8 halt 6 3240000 +8 halt 7 3250000 +8 halt 8 3240000 +8 halt 9 3240000 +8 halt 10 3250000 +8 halt 11 3240000 +8 halt 12 3260000 +8 halt 13 3240000 +8 halt 14 3250000 +8 halt 15 3250000 +8 halt 16 3240000 +8 halt 17 3250000 +8 halt 18 3250000 +8 halt 19 3240000 +8 halt 20 3250000 +8 halt 21 3240000 +8 halt 22 3250000 +8 halt 23 3240000 +8 halt 24 3240000 +8 halt 25 3250000 +8 halt 26 3240000 +8 halt 27 3260000 +8 halt 28 3240000 +8 halt 29 3240000 +8 halt 30 3240000 +# point pass=8 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998181 +9 run 1 3840000 +9 run 2 3840000 +9 run 3 3830000 +9 run 4 3840000 +9 run 5 3830000 +9 run 6 3830000 +9 run 7 3840000 +9 run 8 3840000 +9 run 9 3840000 +9 run 10 3830000 +9 run 11 3830000 +9 run 12 3830000 +9 run 13 3840000 +9 run 14 3830000 +9 run 15 3850000 +9 run 16 3830000 +9 run 17 3830000 +9 run 18 3840000 +9 run 19 3840000 +9 run 20 3850000 +9 run 21 3830000 +9 run 22 3830000 +9 run 23 3850000 +9 run 24 3850000 +9 run 25 3840000 +9 run 26 3840000 +9 run 27 3830000 +9 run 28 3830000 +9 run 29 3840000 +9 run 30 3840000 +# point pass=9 state=run jobs_per_s=12499771.7 updates_per_s=0.0 clock_hz=99998152 +9 halt 1 3250000 +9 halt 2 3260000 +9 halt 3 3260000 +9 halt 4 3270000 +9 halt 5 3250000 +9 halt 6 3250000 +9 halt 7 3250000 +9 halt 8 3250000 +9 halt 9 3250000 +9 halt 10 3250000 +9 halt 11 3260000 +9 halt 12 3270000 +9 halt 13 3250000 +9 halt 14 3250000 +9 halt 15 3260000 +9 halt 16 3250000 +9 halt 17 3260000 +9 halt 18 3260000 +9 halt 19 3260000 +9 halt 20 3280000 +9 halt 21 3280000 +9 halt 22 3250000 +9 halt 23 3250000 +9 halt 24 3260000 +9 halt 25 3260000 +9 halt 26 3250000 +9 halt 27 3250000 +9 halt 28 3250000 +9 halt 29 3280000 +9 halt 30 3260000 +# point pass=9 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998128 +10 run 1 3840000 +10 run 2 3830000 +10 run 3 3840000 +10 run 4 3850000 +10 run 5 3830000 +10 run 6 3840000 +10 run 7 3840000 +10 run 8 3840000 +10 run 9 3860000 +10 run 10 3840000 +10 run 11 3840000 +10 run 12 3830000 +10 run 13 3840000 +10 run 14 3840000 +10 run 15 3840000 +10 run 16 3850000 +10 run 17 3870000 +10 run 18 3840000 +10 run 19 3840000 +10 run 20 3840000 +10 run 21 3840000 +10 run 22 3840000 +10 run 23 3840000 +10 run 24 3840000 +10 run 25 3840000 +10 run 26 3840000 +10 run 27 3840000 +10 run 28 3840000 +10 run 29 3840000 +10 run 30 3850000 +# point pass=10 state=run jobs_per_s=12499766.3 updates_per_s=0.0 clock_hz=99998107 +10 halt 1 3250000 +10 halt 2 3260000 +10 halt 3 3250000 +10 halt 4 3260000 +10 halt 5 3270000 +10 halt 6 3270000 +10 halt 7 3240000 +10 halt 8 3250000 +10 halt 9 3260000 +10 halt 10 3250000 +10 halt 11 3250000 +10 halt 12 3250000 +10 halt 13 3250000 +10 halt 14 3270000 +10 halt 15 3250000 +10 halt 16 3240000 +10 halt 17 3250000 +10 halt 18 3250000 +10 halt 19 3250000 +10 halt 20 3250000 +10 halt 21 3240000 +10 halt 22 3250000 +10 halt 23 3240000 +10 halt 24 3240000 +10 halt 25 3240000 +10 halt 26 3240000 +10 halt 27 3240000 +10 halt 28 3250000 +10 halt 29 3250000 +10 halt 30 3250000 +# point pass=10 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998091 +11 run 1 3850000 +11 run 2 3850000 +11 run 3 3840000 +11 run 4 3840000 +11 run 5 3840000 +11 run 6 3840000 +11 run 7 3830000 +11 run 8 3840000 +11 run 9 3830000 +11 run 10 3830000 +11 run 11 3850000 +11 run 12 3840000 +11 run 13 3830000 +11 run 14 3850000 +11 run 15 3830000 +11 run 16 3840000 +11 run 17 3840000 +11 run 18 3840000 +11 run 19 3840000 +11 run 20 3840000 +11 run 21 3850000 +11 run 22 3830000 +11 run 23 3830000 +11 run 24 3830000 +11 run 25 3840000 +11 run 26 4180000 +11 run 27 3840000 +11 run 28 3820000 +11 run 29 3830000 +11 run 30 3830000 +# point pass=11 state=run jobs_per_s=12499762.5 updates_per_s=0.0 clock_hz=99998076 +11 halt 1 3250000 +11 halt 2 3250000 +11 halt 3 3390000 +11 halt 4 3240000 +11 halt 5 3250000 +11 halt 6 3250000 +11 halt 7 3240000 +11 halt 8 3250000 +11 halt 9 3250000 +11 halt 10 3240000 +11 halt 11 3250000 +11 halt 12 3250000 +11 halt 13 3250000 +11 halt 14 3260000 +11 halt 15 3250000 +11 halt 16 3250000 +11 halt 17 3250000 +11 halt 18 3250000 +11 halt 19 3250000 +11 halt 20 3250000 +11 halt 21 3260000 +11 halt 22 3270000 +11 halt 23 3270000 +11 halt 24 3250000 +11 halt 25 3250000 +11 halt 26 3250000 +11 halt 27 3240000 +11 halt 28 3250000 +11 halt 29 3250000 +11 halt 30 3250000 +# point pass=11 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998063 +12 run 1 3820000 +12 run 2 3820000 +12 run 3 3840000 +12 run 4 3820000 +12 run 5 3830000 +12 run 6 3830000 +12 run 7 3830000 +12 run 8 3830000 +12 run 9 3820000 +12 run 10 3830000 +12 run 11 3840000 +12 run 12 3820000 +12 run 13 3830000 +12 run 14 3820000 +12 run 15 3820000 +12 run 16 3830000 +12 run 17 3830000 +12 run 18 3820000 +12 run 19 3840000 +12 run 20 3830000 +12 run 21 3820000 +12 run 22 3830000 +12 run 23 3830000 +12 run 24 3830000 +12 run 25 3810000 +12 run 26 3810000 +12 run 27 3820000 +12 run 28 3840000 +12 run 29 3820000 +12 run 30 3810000 +# point pass=12 state=run jobs_per_s=12499759.3 updates_per_s=0.0 clock_hz=99998053 +12 halt 1 3250000 +12 halt 2 3250000 +12 halt 3 3250000 +12 halt 4 3250000 +12 halt 5 3250000 +12 halt 6 3250000 +12 halt 7 3250000 +12 halt 8 3270000 +12 halt 9 3250000 +12 halt 10 3240000 +12 halt 11 3240000 +12 halt 12 3240000 +12 halt 13 3250000 +12 halt 14 3240000 +12 halt 15 3250000 +12 halt 16 3260000 +12 halt 17 3250000 +12 halt 18 3250000 +12 halt 19 3240000 +12 halt 20 3240000 +12 halt 21 3250000 +12 halt 22 3240000 +12 halt 23 3250000 +12 halt 24 3250000 +12 halt 25 3240000 +12 halt 26 3250000 +12 halt 27 3240000 +12 halt 28 3240000 +12 halt 29 3250000 +12 halt 30 3240000 +# point pass=12 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998045 +# done SystemTime { tv_sec: 1793922349, tv_nsec: 231452861 } +# passes used 12 of 12 (dropped: []) +# run - halt = 585.8 +/- 1.8 mW (per-pass sd 6.3); 12/12 passes positive; 6 sample excursions beyond 6 MAD +# per-pass differences (mW): 580.0 580.0 590.0 590.0 580.0 580.0 585.0 600.0 585.0 590.0 590.0 580.0 +# board power while running 3835 mW; jobs/s 12499755.9; updates/s 0.0; measured PL clock 99.998 MHz +# pre-registered prediction 488.0 mW: measured/predicted = 1.20, 53.5 sigma from it +# jobs/s and updates/s above are core 0's; all 4 cores run the same job diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/t0-recall-50MHz.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/t0-recall-50MHz.dat new file mode 100644 index 000000000..23565a0f9 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/t0-recall-50MHz.dat @@ -0,0 +1,505 @@ +# class t0-recall +# vqpu-edge meter passes=8 settle=60s samples=30 null=false sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793918156, tv_nsec: 433522612 } +# pass state sample power_uW +1 halt 1 3100000 +1 halt 2 3090000 +1 halt 3 3110000 +1 halt 4 3100000 +1 halt 5 3100000 +1 halt 6 3100000 +1 halt 7 3130000 +1 halt 8 3110000 +1 halt 9 3100000 +1 halt 10 3100000 +1 halt 11 3110000 +1 halt 12 3110000 +1 halt 13 3100000 +1 halt 14 3090000 +1 halt 15 3090000 +1 halt 16 3100000 +1 halt 17 3100000 +1 halt 18 3100000 +1 halt 19 3080000 +1 halt 20 3110000 +1 halt 21 3100000 +1 halt 22 3110000 +1 halt 23 3100000 +1 halt 24 3090000 +1 halt 25 3090000 +1 halt 26 3080000 +1 halt 27 3090000 +1 halt 28 3090000 +1 halt 29 3090000 +1 halt 30 3110000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=49999004 +1 run 1 3160000 +1 run 2 3160000 +1 run 3 3160000 +1 run 4 3170000 +1 run 5 3160000 +1 run 6 3160000 +1 run 7 3160000 +1 run 8 3160000 +1 run 9 3160000 +1 run 10 3160000 +1 run 11 3160000 +1 run 12 3160000 +1 run 13 3160000 +1 run 14 3160000 +1 run 15 3160000 +1 run 16 3160000 +1 run 17 3160000 +1 run 18 3160000 +1 run 19 3170000 +1 run 20 3160000 +1 run 21 3160000 +1 run 22 3160000 +1 run 23 3160000 +1 run 24 3160000 +1 run 25 3160000 +1 run 26 3160000 +1 run 27 3160000 +1 run 28 3180000 +1 run 29 3170000 +1 run 30 3160000 +# point pass=1 state=run jobs_per_s=6249876.9 updates_per_s=0.0 clock_hz=49999004 +2 halt 1 3100000 +2 halt 2 3080000 +2 halt 3 3090000 +2 halt 4 3090000 +2 halt 5 3080000 +2 halt 6 3080000 +2 halt 7 3080000 +2 halt 8 3080000 +2 halt 9 3080000 +2 halt 10 3080000 +2 halt 11 3080000 +2 halt 12 3090000 +2 halt 13 3080000 +2 halt 14 3080000 +2 halt 15 3100000 +2 halt 16 3100000 +2 halt 17 3080000 +2 halt 18 3080000 +2 halt 19 3080000 +2 halt 20 3080000 +2 halt 21 3080000 +2 halt 22 3080000 +2 halt 23 3090000 +2 halt 24 3110000 +2 halt 25 3080000 +2 halt 26 3090000 +2 halt 27 3080000 +2 halt 28 3080000 +2 halt 29 3100000 +2 halt 30 3080000 +# point pass=2 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=49999003 +2 run 1 3150000 +2 run 2 3150000 +2 run 3 3150000 +2 run 4 3170000 +2 run 5 3150000 +2 run 6 3150000 +2 run 7 3160000 +2 run 8 3150000 +2 run 9 3160000 +2 run 10 3150000 +2 run 11 3150000 +2 run 12 3180000 +2 run 13 3170000 +2 run 14 3150000 +2 run 15 3160000 +2 run 16 3150000 +2 run 17 3160000 +2 run 18 3150000 +2 run 19 3150000 +2 run 20 3160000 +2 run 21 3180000 +2 run 22 3160000 +2 run 23 3160000 +2 run 24 3160000 +2 run 25 3160000 +2 run 26 3160000 +2 run 27 3160000 +2 run 28 3160000 +2 run 29 3160000 +2 run 30 3160000 +# point pass=2 state=run jobs_per_s=6249876.7 updates_per_s=0.0 clock_hz=49999003 +3 run 1 3180000 +3 run 2 3170000 +3 run 3 3160000 +3 run 4 3160000 +3 run 5 3160000 +3 run 6 3170000 +3 run 7 3170000 +3 run 8 3160000 +3 run 9 3180000 +3 run 10 3200000 +3 run 11 3160000 +3 run 12 3170000 +3 run 13 3170000 +3 run 14 3170000 +3 run 15 3170000 +3 run 16 3160000 +3 run 17 3160000 +3 run 18 3180000 +3 run 19 3160000 +3 run 20 3160000 +3 run 21 3160000 +3 run 22 3160000 +3 run 23 3160000 +3 run 24 3150000 +3 run 25 3160000 +3 run 26 3160000 +3 run 27 3150000 +3 run 28 3160000 +3 run 29 3160000 +3 run 30 3160000 +# point pass=3 state=run jobs_per_s=6249876.7 updates_per_s=0.0 clock_hz=49999002 +3 halt 1 3100000 +3 halt 2 3080000 +3 halt 3 3100000 +3 halt 4 3090000 +3 halt 5 3100000 +3 halt 6 3110000 +3 halt 7 3100000 +3 halt 8 3100000 +3 halt 9 3100000 +3 halt 10 3090000 +3 halt 11 3100000 +3 halt 12 3100000 +3 halt 13 3090000 +3 halt 14 3100000 +3 halt 15 3100000 +3 halt 16 3110000 +3 halt 17 3100000 +3 halt 18 3100000 +3 halt 19 3090000 +3 halt 20 3100000 +3 halt 21 3100000 +3 halt 22 3090000 +3 halt 23 3100000 +3 halt 24 3100000 +3 halt 25 3100000 +3 halt 26 3100000 +3 halt 27 3090000 +3 halt 28 3100000 +3 halt 29 3110000 +3 halt 30 3110000 +# point pass=3 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=49999003 +4 run 1 3170000 +4 run 2 3170000 +4 run 3 3190000 +4 run 4 3170000 +4 run 5 3170000 +4 run 6 3170000 +4 run 7 3170000 +4 run 8 3170000 +4 run 9 3170000 +4 run 10 3170000 +4 run 11 3170000 +4 run 12 3160000 +4 run 13 3170000 +4 run 14 3160000 +4 run 15 3160000 +4 run 16 3170000 +4 run 17 3170000 +4 run 18 3180000 +4 run 19 3160000 +4 run 20 3180000 +4 run 21 3170000 +4 run 22 3170000 +4 run 23 3160000 +4 run 24 3170000 +4 run 25 3170000 +4 run 26 3190000 +4 run 27 3190000 +4 run 28 3160000 +4 run 29 3160000 +4 run 30 3160000 +# point pass=4 state=run jobs_per_s=6249876.6 updates_per_s=0.0 clock_hz=49999001 +4 halt 1 3090000 +4 halt 2 3080000 +4 halt 3 3100000 +4 halt 4 3090000 +4 halt 5 3100000 +4 halt 6 3080000 +4 halt 7 3090000 +4 halt 8 3090000 +4 halt 9 3100000 +4 halt 10 3100000 +4 halt 11 3100000 +4 halt 12 3090000 +4 halt 13 3080000 +4 halt 14 3090000 +4 halt 15 3110000 +4 halt 16 3080000 +4 halt 17 3080000 +4 halt 18 3100000 +4 halt 19 3090000 +4 halt 20 3080000 +4 halt 21 3090000 +4 halt 22 3090000 +4 halt 23 3110000 +4 halt 24 3100000 +4 halt 25 3110000 +4 halt 26 3100000 +4 halt 27 3080000 +4 halt 28 3090000 +4 halt 29 3080000 +4 halt 30 3090000 +# point pass=4 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=49999001 +5 run 1 3170000 +5 run 2 3170000 +5 run 3 3180000 +5 run 4 3180000 +5 run 5 3160000 +5 run 6 3160000 +5 run 7 3180000 +5 run 8 3170000 +5 run 9 3160000 +5 run 10 3170000 +5 run 11 3160000 +5 run 12 3180000 +5 run 13 3190000 +5 run 14 3170000 +5 run 15 3160000 +5 run 16 3170000 +5 run 17 3180000 +5 run 18 3170000 +5 run 19 3170000 +5 run 20 3170000 +5 run 21 3190000 +5 run 22 3200000 +5 run 23 3170000 +5 run 24 3170000 +5 run 25 3170000 +5 run 26 3170000 +5 run 27 3180000 +5 run 28 3170000 +5 run 29 3170000 +5 run 30 3180000 +# point pass=5 state=run jobs_per_s=6249876.4 updates_per_s=0.0 clock_hz=49999001 +5 halt 1 3090000 +5 halt 2 3100000 +5 halt 3 3090000 +5 halt 4 3100000 +5 halt 5 3090000 +5 halt 6 3090000 +5 halt 7 3100000 +5 halt 8 3100000 +5 halt 9 3090000 +5 halt 10 3100000 +5 halt 11 3100000 +5 halt 12 3420000 +5 halt 13 3090000 +5 halt 14 3120000 +5 halt 15 3080000 +5 halt 16 3090000 +5 halt 17 3180000 +5 halt 18 3110000 +5 halt 19 3090000 +5 halt 20 3090000 +5 halt 21 3080000 +5 halt 22 3090000 +5 halt 23 3090000 +5 halt 24 3090000 +5 halt 25 3090000 +5 halt 26 3080000 +5 halt 27 3090000 +5 halt 28 3100000 +5 halt 29 3090000 +5 halt 30 3100000 +# point pass=5 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=49999000 +6 halt 1 3090000 +6 halt 2 3090000 +6 halt 3 3080000 +6 halt 4 3090000 +6 halt 5 3080000 +6 halt 6 3120000 +6 halt 7 3100000 +6 halt 8 3080000 +6 halt 9 3090000 +6 halt 10 3090000 +6 halt 11 3080000 +6 halt 12 3090000 +6 halt 13 3100000 +6 halt 14 3100000 +6 halt 15 3110000 +6 halt 16 3100000 +6 halt 17 3100000 +6 halt 18 3100000 +6 halt 19 3090000 +6 halt 20 3100000 +6 halt 21 3100000 +6 halt 22 3090000 +6 halt 23 3100000 +6 halt 24 3080000 +6 halt 25 3090000 +6 halt 26 3100000 +6 halt 27 3100000 +6 halt 28 3090000 +6 halt 29 3200000 +6 halt 30 3110000 +# point pass=6 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=49999000 +6 run 1 3170000 +6 run 2 3160000 +6 run 3 3190000 +6 run 4 3190000 +6 run 5 3160000 +6 run 6 3160000 +6 run 7 3160000 +6 run 8 3170000 +6 run 9 3170000 +6 run 10 3160000 +6 run 11 3180000 +6 run 12 3190000 +6 run 13 3160000 +6 run 14 3170000 +6 run 15 3160000 +6 run 16 3170000 +6 run 17 3160000 +6 run 18 3170000 +6 run 19 3160000 +6 run 20 3170000 +6 run 21 3170000 +6 run 22 3160000 +6 run 23 3160000 +6 run 24 3160000 +6 run 25 3160000 +6 run 26 3170000 +6 run 27 3170000 +6 run 28 3170000 +6 run 29 3170000 +6 run 30 3170000 +# point pass=6 state=run jobs_per_s=6249876.5 updates_per_s=0.0 clock_hz=49999000 +7 halt 1 3100000 +7 halt 2 3080000 +7 halt 3 3080000 +7 halt 4 3090000 +7 halt 5 3090000 +7 halt 6 3100000 +7 halt 7 3100000 +7 halt 8 3100000 +7 halt 9 3100000 +7 halt 10 3090000 +7 halt 11 3100000 +7 halt 12 3100000 +7 halt 13 3090000 +7 halt 14 3100000 +7 halt 15 3110000 +7 halt 16 3100000 +7 halt 17 3080000 +7 halt 18 3090000 +7 halt 19 3090000 +7 halt 20 3080000 +7 halt 21 3090000 +7 halt 22 3090000 +7 halt 23 3110000 +7 halt 24 3110000 +7 halt 25 3100000 +7 halt 26 3090000 +7 halt 27 3090000 +7 halt 28 3100000 +7 halt 29 3100000 +7 halt 30 3100000 +# point pass=7 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=49999001 +7 run 1 3160000 +7 run 2 3160000 +7 run 3 3160000 +7 run 4 3160000 +7 run 5 3160000 +7 run 6 3160000 +7 run 7 3160000 +7 run 8 3170000 +7 run 9 3160000 +7 run 10 3160000 +7 run 11 3160000 +7 run 12 3180000 +7 run 13 3160000 +7 run 14 3160000 +7 run 15 3170000 +7 run 16 3160000 +7 run 17 3160000 +7 run 18 3160000 +7 run 19 3160000 +7 run 20 3170000 +7 run 21 3190000 +7 run 22 3160000 +7 run 23 3150000 +7 run 24 3160000 +7 run 25 3160000 +7 run 26 3160000 +7 run 27 3160000 +7 run 28 3160000 +7 run 29 3180000 +7 run 30 3160000 +# point pass=7 state=run jobs_per_s=6249864.5 updates_per_s=0.0 clock_hz=49998905 +8 run 1 3170000 +8 run 2 3170000 +8 run 3 3170000 +8 run 4 3170000 +8 run 5 3170000 +8 run 6 3160000 +8 run 7 3170000 +8 run 8 3170000 +8 run 9 3190000 +8 run 10 3160000 +8 run 11 3160000 +8 run 12 3160000 +8 run 13 3170000 +8 run 14 3160000 +8 run 15 3170000 +8 run 16 3160000 +8 run 17 3190000 +8 run 18 3170000 +8 run 19 3160000 +8 run 20 3160000 +8 run 21 3160000 +8 run 22 3170000 +8 run 23 3160000 +8 run 24 3160000 +8 run 25 3160000 +8 run 26 3160000 +8 run 27 3170000 +8 run 28 3170000 +8 run 29 3160000 +8 run 30 3170000 +# point pass=8 state=run jobs_per_s=6249866.0 updates_per_s=0.0 clock_hz=49998917 +8 halt 1 3090000 +8 halt 2 3100000 +8 halt 3 3090000 +8 halt 4 3100000 +8 halt 5 3110000 +8 halt 6 3110000 +8 halt 7 3100000 +8 halt 8 3100000 +8 halt 9 3090000 +8 halt 10 3100000 +8 halt 11 3090000 +8 halt 12 3100000 +8 halt 13 3090000 +8 halt 14 3110000 +8 halt 15 3100000 +8 halt 16 3090000 +8 halt 17 3100000 +8 halt 18 3100000 +8 halt 19 3090000 +8 halt 20 3100000 +8 halt 21 3090000 +8 halt 22 3100000 +8 halt 23 3090000 +8 halt 24 3090000 +8 halt 25 3100000 +8 halt 26 3090000 +8 halt 27 3110000 +8 halt 28 3100000 +8 halt 29 3110000 +8 halt 30 3090000 +# point pass=8 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=49998928 +# done SystemTime { tv_sec: 1793919597, tv_nsec: 315661140 } +# passes used 8 of 8 (dropped: []) +# run - halt = 70.6 +/- 3.3 mW (per-pass sd 9.4); 8/8 passes positive; 4 sample excursions beyond 6 MAD +# per-pass differences (mW): 60.0 80.0 60.0 80.0 80.0 75.0 60.0 70.0 +# board power while running 3165 mW; jobs/s 6249873.8; updates/s 0.0; measured PL clock 49.999 MHz +# pre-registered prediction 61.0 mW: measured/predicted = 1.16, 2.9 sigma from it diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/t0-recall.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/t0-recall.dat new file mode 100644 index 000000000..8c5b8e6af --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/t0-recall.dat @@ -0,0 +1,753 @@ +# class t0-recall +# vqpu-edge meter passes=12 settle=60s samples=30 null=false sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793910950, tv_nsec: 505668490 } +# pass state sample power_uW +1 run 1 3260000 +1 run 2 3250000 +1 run 3 3270000 +1 run 4 3260000 +1 run 5 3260000 +1 run 6 3260000 +1 run 7 3260000 +1 run 8 3260000 +1 run 9 3250000 +1 run 10 3250000 +1 run 11 3260000 +1 run 12 3250000 +1 run 13 3250000 +1 run 14 3260000 +1 run 15 3260000 +1 run 16 3260000 +1 run 17 3250000 +1 run 18 3260000 +1 run 19 3260000 +1 run 20 3250000 +1 run 21 3250000 +1 run 22 3260000 +1 run 23 3260000 +1 run 24 3250000 +1 run 25 3270000 +1 run 26 3280000 +1 run 27 3250000 +1 run 28 3260000 +1 run 29 3260000 +1 run 30 3260000 +# point pass=1 state=run jobs_per_s=12499750.0 updates_per_s=0.0 clock_hz=99997978 +1 halt 1 3120000 +1 halt 2 3110000 +1 halt 3 3100000 +1 halt 4 3110000 +1 halt 5 3110000 +1 halt 6 3110000 +1 halt 7 3120000 +1 halt 8 3130000 +1 halt 9 3110000 +1 halt 10 3110000 +1 halt 11 3110000 +1 halt 12 3110000 +1 halt 13 3100000 +1 halt 14 3120000 +1 halt 15 3110000 +1 halt 16 3110000 +1 halt 17 3110000 +1 halt 18 3110000 +1 halt 19 3110000 +1 halt 20 3110000 +1 halt 21 3100000 +1 halt 22 3110000 +1 halt 23 3130000 +1 halt 24 3110000 +1 halt 25 3110000 +1 halt 26 3110000 +1 halt 27 3100000 +1 halt 28 3110000 +1 halt 29 3100000 +1 halt 30 3110000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997978 +2 run 1 3260000 +2 run 2 3260000 +2 run 3 3260000 +2 run 4 3260000 +2 run 5 3270000 +2 run 6 3250000 +2 run 7 3250000 +2 run 8 3260000 +2 run 9 3250000 +2 run 10 3250000 +2 run 11 3260000 +2 run 12 3250000 +2 run 13 3260000 +2 run 14 3260000 +2 run 15 3260000 +2 run 16 3260000 +2 run 17 3260000 +2 run 18 3250000 +2 run 19 3260000 +2 run 20 3290000 +2 run 21 3250000 +2 run 22 3250000 +2 run 23 3250000 +2 run 24 3260000 +2 run 25 3260000 +2 run 26 3260000 +2 run 27 3270000 +2 run 28 3280000 +2 run 29 3260000 +2 run 30 3260000 +# point pass=2 state=run jobs_per_s=12499741.8 updates_per_s=0.0 clock_hz=99997912 +2 halt 1 3110000 +2 halt 2 3110000 +2 halt 3 3110000 +2 halt 4 3100000 +2 halt 5 3100000 +2 halt 6 3110000 +2 halt 7 3110000 +2 halt 8 3120000 +2 halt 9 3100000 +2 halt 10 3110000 +2 halt 11 3120000 +2 halt 12 3110000 +2 halt 13 3110000 +2 halt 14 3120000 +2 halt 15 3110000 +2 halt 16 3120000 +2 halt 17 3140000 +2 halt 18 3110000 +2 halt 19 3110000 +2 halt 20 3110000 +2 halt 21 3110000 +2 halt 22 3110000 +2 halt 23 3110000 +2 halt 24 3110000 +2 halt 25 3120000 +2 halt 26 3110000 +2 halt 27 3110000 +2 halt 28 3110000 +2 halt 29 3110000 +2 halt 30 3110000 +# point pass=2 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997921 +3 halt 1 3120000 +3 halt 2 3110000 +3 halt 3 3110000 +3 halt 4 3110000 +3 halt 5 3130000 +3 halt 6 3110000 +3 halt 7 3110000 +3 halt 8 3100000 +3 halt 9 3110000 +3 halt 10 3110000 +3 halt 11 3120000 +3 halt 12 3110000 +3 halt 13 3120000 +3 halt 14 3110000 +3 halt 15 3120000 +3 halt 16 3110000 +3 halt 17 3110000 +3 halt 18 3100000 +3 halt 19 3110000 +3 halt 20 3110000 +3 halt 21 3120000 +3 halt 22 3110000 +3 halt 23 3110000 +3 halt 24 3110000 +3 halt 25 3110000 +3 halt 26 3110000 +3 halt 27 3120000 +3 halt 28 3110000 +3 halt 29 3100000 +3 halt 30 3110000 +# point pass=3 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997928 +3 run 1 3260000 +3 run 2 3280000 +3 run 3 3250000 +3 run 4 3250000 +3 run 5 3250000 +3 run 6 3260000 +3 run 7 3250000 +3 run 8 3260000 +3 run 9 3250000 +3 run 10 3270000 +3 run 11 3250000 +3 run 12 3260000 +3 run 13 3270000 +3 run 14 3260000 +3 run 15 3260000 +3 run 16 3260000 +3 run 17 3260000 +3 run 18 3270000 +3 run 19 3270000 +3 run 20 3260000 +3 run 21 3260000 +3 run 22 3260000 +3 run 23 3270000 +3 run 24 3250000 +3 run 25 3290000 +3 run 26 3270000 +3 run 27 3260000 +3 run 28 3260000 +3 run 29 3260000 +3 run 30 3260000 +# point pass=3 state=run jobs_per_s=12499744.6 updates_per_s=0.0 clock_hz=99997935 +4 run 1 3250000 +4 run 2 3260000 +4 run 3 3260000 +4 run 4 3250000 +4 run 5 3260000 +4 run 6 3250000 +4 run 7 3290000 +4 run 8 3260000 +4 run 9 3250000 +4 run 10 3260000 +4 run 11 3260000 +4 run 12 3250000 +4 run 13 3250000 +4 run 14 3250000 +4 run 15 3250000 +4 run 16 3260000 +4 run 17 3260000 +4 run 18 3260000 +4 run 19 3260000 +4 run 20 3250000 +4 run 21 3270000 +4 run 22 3260000 +4 run 23 3250000 +4 run 24 3260000 +4 run 25 3250000 +4 run 26 3500000 +4 run 27 3260000 +4 run 28 3260000 +4 run 29 3260000 +4 run 30 3270000 +# point pass=4 state=run jobs_per_s=12499745.3 updates_per_s=0.0 clock_hz=99997940 +4 halt 1 3100000 +4 halt 2 3100000 +4 halt 3 3110000 +4 halt 4 3110000 +4 halt 5 3100000 +4 halt 6 3100000 +4 halt 7 3110000 +4 halt 8 3110000 +4 halt 9 3100000 +4 halt 10 3110000 +4 halt 11 3110000 +4 halt 12 3100000 +4 halt 13 3100000 +4 halt 14 3110000 +4 halt 15 3110000 +4 halt 16 3120000 +4 halt 17 3120000 +4 halt 18 3130000 +4 halt 19 3140000 +4 halt 20 3110000 +4 halt 21 3110000 +4 halt 22 3110000 +4 halt 23 3110000 +4 halt 24 3110000 +4 halt 25 3110000 +4 halt 26 3110000 +4 halt 27 3130000 +4 halt 28 3110000 +4 halt 29 3110000 +4 halt 30 3110000 +# point pass=4 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997942 +5 run 1 3260000 +5 run 2 3260000 +5 run 3 3280000 +5 run 4 3260000 +5 run 5 3260000 +5 run 6 3270000 +5 run 7 3290000 +5 run 8 3270000 +5 run 9 3260000 +5 run 10 3270000 +5 run 11 3260000 +5 run 12 3260000 +5 run 13 3270000 +5 run 14 3260000 +5 run 15 3290000 +5 run 16 3290000 +5 run 17 3270000 +5 run 18 3260000 +5 run 19 3260000 +5 run 20 3270000 +5 run 21 3270000 +5 run 22 3260000 +5 run 23 3270000 +5 run 24 3280000 +5 run 25 3260000 +5 run 26 3260000 +5 run 27 3270000 +5 run 28 3270000 +5 run 29 3260000 +5 run 30 3270000 +# point pass=5 state=run jobs_per_s=12499746.0 updates_per_s=0.0 clock_hz=99997946 +5 halt 1 3120000 +5 halt 2 3120000 +5 halt 3 3120000 +5 halt 4 3140000 +5 halt 5 3120000 +5 halt 6 3110000 +5 halt 7 3130000 +5 halt 8 3120000 +5 halt 9 3110000 +5 halt 10 3130000 +5 halt 11 3130000 +5 halt 12 3140000 +5 halt 13 3120000 +5 halt 14 3110000 +5 halt 15 3130000 +5 halt 16 3110000 +5 halt 17 3110000 +5 halt 18 3110000 +5 halt 19 3110000 +5 halt 20 3120000 +5 halt 21 3110000 +5 halt 22 3120000 +5 halt 23 3110000 +5 halt 24 3110000 +5 halt 25 3100000 +5 halt 26 3110000 +5 halt 27 3120000 +5 halt 28 3100000 +5 halt 29 3100000 +5 halt 30 3110000 +# point pass=5 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997949 +6 halt 1 3140000 +6 halt 2 3500000 +6 halt 3 3100000 +6 halt 4 3110000 +6 halt 5 3120000 +6 halt 6 3120000 +6 halt 7 3110000 +6 halt 8 3120000 +6 halt 9 3130000 +6 halt 10 3110000 +6 halt 11 3110000 +6 halt 12 3110000 +6 halt 13 3120000 +6 halt 14 3110000 +6 halt 15 3120000 +6 halt 16 3120000 +6 halt 17 3110000 +6 halt 18 3120000 +6 halt 19 3120000 +6 halt 20 3110000 +6 halt 21 3110000 +6 halt 22 3110000 +6 halt 23 3120000 +6 halt 24 3130000 +6 halt 25 3120000 +6 halt 26 3110000 +6 halt 27 3120000 +6 halt 28 3120000 +6 halt 29 3110000 +6 halt 30 3110000 +# point pass=6 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997951 +6 run 1 3250000 +6 run 2 3260000 +6 run 3 3250000 +6 run 4 3260000 +6 run 5 3250000 +6 run 6 3280000 +6 run 7 3250000 +6 run 8 3250000 +6 run 9 3260000 +6 run 10 3260000 +6 run 11 3250000 +6 run 12 3250000 +6 run 13 3250000 +6 run 14 3260000 +6 run 15 3250000 +6 run 16 3250000 +6 run 17 3250000 +6 run 18 3250000 +6 run 19 3250000 +6 run 20 3260000 +6 run 21 3270000 +6 run 22 3250000 +6 run 23 3260000 +6 run 24 3260000 +6 run 25 3260000 +6 run 26 3260000 +6 run 27 3270000 +6 run 28 3260000 +6 run 29 3290000 +6 run 30 3260000 +# point pass=6 state=run jobs_per_s=12499746.9 updates_per_s=0.0 clock_hz=99997953 +7 halt 1 3100000 +7 halt 2 3110000 +7 halt 3 3100000 +7 halt 4 3110000 +7 halt 5 3100000 +7 halt 6 3100000 +7 halt 7 3090000 +7 halt 8 3110000 +7 halt 9 3110000 +7 halt 10 3100000 +7 halt 11 3100000 +7 halt 12 3110000 +7 halt 13 3100000 +7 halt 14 3110000 +7 halt 15 3110000 +7 halt 16 3110000 +7 halt 17 3120000 +7 halt 18 3120000 +7 halt 19 3100000 +7 halt 20 3100000 +7 halt 21 3090000 +7 halt 22 3100000 +7 halt 23 3110000 +7 halt 24 3110000 +7 halt 25 3110000 +7 halt 26 3120000 +7 halt 27 3100000 +7 halt 28 3100000 +7 halt 29 3100000 +7 halt 30 3110000 +# point pass=7 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997955 +7 run 1 3240000 +7 run 2 3250000 +7 run 3 3250000 +7 run 4 3270000 +7 run 5 3250000 +7 run 6 3270000 +7 run 7 3250000 +7 run 8 3260000 +7 run 9 3240000 +7 run 10 3250000 +7 run 11 3250000 +7 run 12 3240000 +7 run 13 3250000 +7 run 14 3270000 +7 run 15 3270000 +7 run 16 3250000 +7 run 17 3260000 +7 run 18 3250000 +7 run 19 3240000 +7 run 20 3250000 +7 run 21 3240000 +7 run 22 3250000 +7 run 23 3270000 +7 run 24 3250000 +7 run 25 3250000 +7 run 26 3250000 +7 run 27 3250000 +7 run 28 3250000 +7 run 29 3250000 +7 run 30 3250000 +# point pass=7 state=run jobs_per_s=12499747.3 updates_per_s=0.0 clock_hz=99997956 +8 halt 1 3110000 +8 halt 2 3110000 +8 halt 3 3120000 +8 halt 4 3110000 +8 halt 5 3110000 +8 halt 6 3110000 +8 halt 7 3110000 +8 halt 8 3110000 +8 halt 9 3130000 +8 halt 10 3100000 +8 halt 11 3120000 +8 halt 12 3110000 +8 halt 13 3110000 +8 halt 14 3100000 +8 halt 15 3100000 +8 halt 16 3100000 +8 halt 17 3110000 +8 halt 18 3100000 +8 halt 19 3110000 +8 halt 20 3120000 +8 halt 21 3110000 +8 halt 22 3110000 +8 halt 23 3110000 +8 halt 24 3110000 +8 halt 25 3110000 +8 halt 26 3120000 +8 halt 27 3110000 +8 halt 28 3120000 +8 halt 29 3120000 +8 halt 30 3120000 +# point pass=8 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997958 +8 run 1 3250000 +8 run 2 3250000 +8 run 3 3250000 +8 run 4 3260000 +8 run 5 3250000 +8 run 6 3250000 +8 run 7 3250000 +8 run 8 3260000 +8 run 9 3250000 +8 run 10 3250000 +8 run 11 3250000 +8 run 12 3250000 +8 run 13 3250000 +8 run 14 3250000 +8 run 15 3250000 +8 run 16 3250000 +8 run 17 3240000 +8 run 18 3250000 +8 run 19 3250000 +8 run 20 3250000 +8 run 21 3250000 +8 run 22 3260000 +8 run 23 3270000 +8 run 24 3250000 +8 run 25 3250000 +8 run 26 3250000 +8 run 27 3250000 +8 run 28 3240000 +8 run 29 3250000 +8 run 30 3260000 +# point pass=8 state=run jobs_per_s=12499747.6 updates_per_s=0.0 clock_hz=99997959 +9 halt 1 3110000 +9 halt 2 3100000 +9 halt 3 3100000 +9 halt 4 3100000 +9 halt 5 3120000 +9 halt 6 3110000 +9 halt 7 3110000 +9 halt 8 3100000 +9 halt 9 3100000 +9 halt 10 3100000 +9 halt 11 3100000 +9 halt 12 3100000 +9 halt 13 3100000 +9 halt 14 3110000 +9 halt 15 3100000 +9 halt 16 3100000 +9 halt 17 3110000 +9 halt 18 3100000 +9 halt 19 3090000 +9 halt 20 3110000 +9 halt 21 3100000 +9 halt 22 3100000 +9 halt 23 3100000 +9 halt 24 3100000 +9 halt 25 3100000 +9 halt 26 3090000 +9 halt 27 3100000 +9 halt 28 3120000 +9 halt 29 3100000 +9 halt 30 3100000 +# point pass=9 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997960 +9 run 1 3260000 +9 run 2 3250000 +9 run 3 3260000 +9 run 4 3260000 +9 run 5 3260000 +9 run 6 3260000 +9 run 7 3260000 +9 run 8 3280000 +9 run 9 3260000 +9 run 10 3250000 +9 run 11 3260000 +9 run 12 3270000 +9 run 13 3260000 +9 run 14 3270000 +9 run 15 3260000 +9 run 16 3280000 +9 run 17 3280000 +9 run 18 3260000 +9 run 19 3260000 +9 run 20 3260000 +9 run 21 3260000 +9 run 22 3260000 +9 run 23 3260000 +9 run 24 3260000 +9 run 25 3280000 +9 run 26 3260000 +9 run 27 3270000 +9 run 28 3260000 +9 run 29 3260000 +9 run 30 3260000 +# point pass=9 state=run jobs_per_s=12499747.8 updates_per_s=0.0 clock_hz=99997960 +10 run 1 3260000 +10 run 2 3260000 +10 run 3 3260000 +10 run 4 3260000 +10 run 5 3270000 +10 run 6 3260000 +10 run 7 3260000 +10 run 8 3260000 +10 run 9 3260000 +10 run 10 3270000 +10 run 11 3260000 +10 run 12 3260000 +10 run 13 3290000 +10 run 14 3280000 +10 run 15 3260000 +10 run 16 3260000 +10 run 17 3260000 +10 run 18 3260000 +10 run 19 3260000 +10 run 20 3270000 +10 run 21 3270000 +10 run 22 3270000 +10 run 23 3260000 +10 run 24 3270000 +10 run 25 3270000 +10 run 26 3260000 +10 run 27 3270000 +10 run 28 3270000 +10 run 29 3260000 +10 run 30 3260000 +# point pass=10 state=run jobs_per_s=12499747.9 updates_per_s=0.0 clock_hz=99997961 +10 halt 1 3140000 +10 halt 2 3140000 +10 halt 3 3120000 +10 halt 4 3120000 +10 halt 5 3120000 +10 halt 6 3120000 +10 halt 7 3120000 +10 halt 8 3120000 +10 halt 9 3120000 +10 halt 10 3130000 +10 halt 11 3120000 +10 halt 12 3120000 +10 halt 13 3110000 +10 halt 14 3110000 +10 halt 15 3110000 +10 halt 16 3110000 +10 halt 17 3110000 +10 halt 18 3110000 +10 halt 19 3120000 +10 halt 20 3110000 +10 halt 21 3110000 +10 halt 22 3110000 +10 halt 23 3110000 +10 halt 24 3110000 +10 halt 25 3140000 +10 halt 26 3110000 +10 halt 27 3110000 +10 halt 28 3120000 +10 halt 29 3120000 +10 halt 30 3110000 +# point pass=10 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997961 +11 halt 1 3120000 +11 halt 2 3120000 +11 halt 3 3120000 +11 halt 4 3120000 +11 halt 5 3110000 +11 halt 6 3120000 +11 halt 7 3130000 +11 halt 8 3110000 +11 halt 9 3120000 +11 halt 10 3110000 +11 halt 11 3110000 +11 halt 12 3120000 +11 halt 13 3120000 +11 halt 14 3120000 +11 halt 15 3130000 +11 halt 16 3120000 +11 halt 17 3120000 +11 halt 18 3120000 +11 halt 19 3120000 +11 halt 20 3120000 +11 halt 21 3120000 +11 halt 22 3140000 +11 halt 23 3110000 +11 halt 24 3130000 +11 halt 25 3130000 +11 halt 26 3110000 +11 halt 27 3120000 +11 halt 28 3130000 +11 halt 29 3120000 +11 halt 30 3140000 +# point pass=11 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997961 +11 run 1 3250000 +11 run 2 3260000 +11 run 3 3260000 +11 run 4 3260000 +11 run 5 3260000 +11 run 6 3260000 +11 run 7 3250000 +11 run 8 3250000 +11 run 9 3260000 +11 run 10 3290000 +11 run 11 3270000 +11 run 12 3260000 +11 run 13 3260000 +11 run 14 3260000 +11 run 15 3590000 +11 run 16 3260000 +11 run 17 3260000 +11 run 18 3280000 +11 run 19 3280000 +11 run 20 3270000 +11 run 21 3260000 +11 run 22 3260000 +11 run 23 3250000 +11 run 24 3250000 +11 run 25 3260000 +11 run 26 3270000 +11 run 27 3280000 +11 run 28 3250000 +11 run 29 3260000 +11 run 30 3260000 +# point pass=11 state=run jobs_per_s=12499748.2 updates_per_s=0.0 clock_hz=99997964 +12 run 1 3250000 +12 run 2 3260000 +12 run 3 3260000 +12 run 4 3260000 +12 run 5 3260000 +12 run 6 3280000 +12 run 7 3260000 +12 run 8 3260000 +12 run 9 3250000 +12 run 10 3260000 +12 run 11 3260000 +12 run 12 3260000 +12 run 13 3300000 +12 run 14 3260000 +12 run 15 3250000 +12 run 16 3260000 +12 run 17 3260000 +12 run 18 3270000 +12 run 19 3260000 +12 run 20 3270000 +12 run 21 3270000 +12 run 22 3280000 +12 run 23 3260000 +12 run 24 3280000 +12 run 25 3270000 +12 run 26 3260000 +12 run 27 3290000 +12 run 28 3270000 +12 run 29 3260000 +12 run 30 3260000 +# point pass=12 state=run jobs_per_s=12499748.3 updates_per_s=0.0 clock_hz=99997964 +12 halt 1 3130000 +12 halt 2 3130000 +12 halt 3 3110000 +12 halt 4 3110000 +12 halt 5 3130000 +12 halt 6 3120000 +12 halt 7 3130000 +12 halt 8 3120000 +12 halt 9 3110000 +12 halt 10 3110000 +12 halt 11 3120000 +12 halt 12 3120000 +12 halt 13 3120000 +12 halt 14 3110000 +12 halt 15 3140000 +12 halt 16 3140000 +12 halt 17 3110000 +12 halt 18 3120000 +12 halt 19 3120000 +12 halt 20 3120000 +12 halt 21 3110000 +12 halt 22 3110000 +12 halt 23 3120000 +12 halt 24 3120000 +12 halt 25 3110000 +12 halt 26 3130000 +12 halt 27 3110000 +12 halt 28 3110000 +12 halt 29 3110000 +12 halt 30 3130000 +# point pass=12 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997964 +# done SystemTime { tv_sec: 1793913111, tv_nsec: 884855982 } +# passes used 12 of 12 (dropped: []) +# run - halt = 146.7 +/- 2.0 mW (per-pass sd 6.9); 12/12 passes positive; 4 sample excursions beyond 6 MAD +# per-pass differences (mW): 150.0 150.0 150.0 150.0 155.0 140.0 145.0 140.0 160.0 140.0 140.0 140.0 +# board power while running 3259 mW; jobs/s 12499746.8; updates/s 0.0; measured PL clock 99.998 MHz +# pre-registered prediction 122.0 mW: measured/predicted = 1.20, 12.5 sigma from it diff --git a/wai/wai-quantum-fpga/measurements/2026-11-06/PREDICTION.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/PREDICTION.txt similarity index 100% rename from wai/wai-quantum-fpga/measurements/2026-11-06/PREDICTION.txt rename to wai/wai-quantum-landscape-fpga/measurements/2026-11-06/PREDICTION.txt diff --git a/wai/wai-quantum-fpga/rtl/landscape.xdc b/wai/wai-quantum-landscape-fpga/rtl/landscape.xdc similarity index 91% rename from wai/wai-quantum-fpga/rtl/landscape.xdc rename to wai/wai-quantum-landscape-fpga/rtl/landscape.xdc index 06866056e..2fb5c0be6 100644 --- a/wai/wai-quantum-fpga/rtl/landscape.xdc +++ b/wai/wai-quantum-landscape-fpga/rtl/landscape.xdc @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 # The EMIO lines cross from the PS's bus clock into pl_clk0. req is synchronised in edge_emio.v; addr, # we and wdata are stable before req is seen; rdata is stable before ack toggles. set_false_path -to [get_cells -hierarchical -filter {NAME =~ *req_s1_reg*}] diff --git a/wai/wai-quantum-fpga/rtl/landscape_core.v b/wai/wai-quantum-landscape-fpga/rtl/landscape_core.v similarity index 99% rename from wai/wai-quantum-fpga/rtl/landscape_core.v rename to wai/wai-quantum-landscape-fpga/rtl/landscape_core.v index 5c104fc69..b70f989e5 100644 --- a/wai/wai-quantum-fpga/rtl/landscape_core.v +++ b/wai/wai-quantum-landscape-fpga/rtl/landscape_core.v @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 // Energy-landscape engine: the two kernels of wai.quantum.landscape/1 (extensions/quantum-landscape.md // section 4) for up to 64 cells, in programmable logic. A classical design: three-state cells, a // counter-based random stream, no quantum effect. diff --git a/wai/wai-quantum-fpga/rtl/landscape_multi.v b/wai/wai-quantum-landscape-fpga/rtl/landscape_multi.v similarity index 97% rename from wai/wai-quantum-fpga/rtl/landscape_multi.v rename to wai/wai-quantum-landscape-fpga/rtl/landscape_multi.v index 299df8577..80f686fa1 100644 --- a/wai/wai-quantum-fpga/rtl/landscape_multi.v +++ b/wai/wai-quantum-landscape-fpga/rtl/landscape_multi.v @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 // K copies of the engine behind one EMIO port: address bits [15:13] pick the core, [12:0] are the // core's own map. busy is ORed and done ANDed across cores. The host addresses one core at a time // (CorePort) or all at once (BroadcastPort). diff --git a/wai/wai-quantum-fpga/rtl/landscape_port.v b/wai/wai-quantum-landscape-fpga/rtl/landscape_port.v similarity index 98% rename from wai/wai-quantum-fpga/rtl/landscape_port.v rename to wai/wai-quantum-landscape-fpga/rtl/landscape_port.v index 8bd76b087..d3f634823 100644 --- a/wai/wai-quantum-fpga/rtl/landscape_port.v +++ b/wai/wai-quantum-landscape-fpga/rtl/landscape_port.v @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 // The job port: register reads and writes carried over the PS's EMIO GPIO lines. // // Why not AXI. A PS load or store to an AXI address whose PL slave is gone never completes, and it diff --git a/wai/wai-quantum-fpga/rtl/landscape_top.v b/wai/wai-quantum-landscape-fpga/rtl/landscape_top.v similarity index 90% rename from wai/wai-quantum-fpga/rtl/landscape_top.v rename to wai/wai-quantum-landscape-fpga/rtl/landscape_top.v index 7fcfa5a6b..6fad282d4 100644 --- a/wai/wai-quantum-fpga/rtl/landscape_top.v +++ b/wai/wai-quantum-landscape-fpga/rtl/landscape_top.v @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 // KV260 top: the PS provides pl_clk0 and the EMIO lines; there is no other PL I/O. `timescale 1ns/1ps module design_top(); diff --git a/wai/wai-quantum-fpga/rtl/landscape_top_multi.v b/wai/wai-quantum-landscape-fpga/rtl/landscape_top_multi.v similarity index 88% rename from wai/wai-quantum-fpga/rtl/landscape_top_multi.v rename to wai/wai-quantum-landscape-fpga/rtl/landscape_top_multi.v index 8488a2a95..5c6c1c53b 100644 --- a/wai/wai-quantum-fpga/rtl/landscape_top_multi.v +++ b/wai/wai-quantum-landscape-fpga/rtl/landscape_top_multi.v @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 `timescale 1ns/1ps module design_top(); wire pl_clk0; diff --git a/wai/wai-quantum-landscape-fpga/scripts/build_bitstream.sh b/wai/wai-quantum-landscape-fpga/scripts/build_bitstream.sh new file mode 100755 index 000000000..3cfca50df --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/scripts/build_bitstream.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# SPDX-License-Identifier: Apache-2.0 +# Build the engine's bitstream and convert it for the Linux FPGA manager. Needs the vendor's tool on +# PATH, at the version this design was built and measured with: Vivado 2026.1. Its no-charge licence +# tier covers this part; the design uses no licensed IP (the processing system's configuration block +# only). Run from an empty directory: +# sh /path/to/wai-quantum-landscape-fpga/scripts/build_bitstream.sh [MHz] [cores] +set -e +FREQ=${1:-100} +CORES=${2:-1} +HERE=$(cd "$(dirname "$0")/.." && pwd) +vivado -version | head -1 +vivado -mode batch -nojournal -source "$HERE/landscape.tcl" -tclargs FREQ "$FREQ" TAG landscape JOBS 4 CORES "$CORES" > build.log 2>&1 +grep -E "IMPL_STATUS|BITSTREAM" build.log +BIT=$(ls ev_landscape/ev_landscape.runs/impl_1/*.bit) +printf 'all:\n{\n %s\n}\n' "$BIT" > landscape.bif +bootgen -image landscape.bif -arch zynqmp -process_bitstream bin -w on +cp "$BIT.bin" landscape.bit.bin +grep -A6 "Design Timing Summary" ev_landscape.timing.rpt | tail -1 +sha256sum landscape.bit.bin diff --git a/wai/wai-quantum-landscape-fpga/scripts/build_host.sh b/wai/wai-quantum-landscape-fpga/scripts/build_host.sh new file mode 100755 index 000000000..74bf98809 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/scripts/build_host.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# SPDX-License-Identifier: Apache-2.0 +# Cross-compile the host for the board's processor (aarch64 Linux, glibc 2.39 on the board's image). +# Needs rustup's aarch64-unknown-linux-gnu target for the repository's pinned toolchain and the +# aarch64-linux-gnu-gcc linker. +set -e +cd "$(dirname "$0")/.." +rustup target add aarch64-unknown-linux-gnu +CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc cargo build --release --target aarch64-unknown-linux-gnu +ls -la "${CARGO_TARGET_DIR:-target}/aarch64-unknown-linux-gnu/release/wai-quantum-landscape-fpga" diff --git a/wai/wai-quantum-fpga/scripts/power_campaign.sh b/wai/wai-quantum-landscape-fpga/scripts/power_campaign.sh similarity index 62% rename from wai/wai-quantum-fpga/scripts/power_campaign.sh rename to wai/wai-quantum-landscape-fpga/scripts/power_campaign.sh index da850222b..d71ad09d7 100755 --- a/wai/wai-quantum-fpga/scripts/power_campaign.sh +++ b/wai/wai-quantum-landscape-fpga/scripts/power_campaign.sh @@ -1,10 +1,11 @@ #!/bin/sh +# SPDX-License-Identifier: Apache-2.0 # The power campaign, in a fixed order: the run/halt step response, a known-answer vehicle where one # is present, a null, each job class, then the clock-scaling check. Run ON the board as root, # detached, and touch nothing on the board until DONE appears (the meter reads the whole board, the # processor included): # sudo setsid nohup sh power_campaign.sh [OUTDIR] > /dev/null 2>&1 & -# Needs wai-quantum-fpga and the loaded engine. A known-answer vehicle of your own goes in +# Needs wai-quantum-landscape-fpga and the loaded engine. A known-answer vehicle of your own goes in # KNOWN_ANSWER (a script taking the bitstream name, settle, passes and samples, printing # "pass run|halt sample microwatts" lines) and KNOWN_BITSTREAM; without them that step is skipped, # and the null and the clock-scaling check are the instrument's checks. @@ -12,19 +13,19 @@ cd "$(dirname "$0")" OUT=${1:-power-$(date -u +%Y%m%dT%H%M%SZ)}/power; mkdir -p "$OUT" BIT=${ENGINE_BITSTREAM:-landscape.bit.bin} log() { echo "$(date -u +%FT%TZ) $*" >> "$OUT/campaign.log"; } -foreign() { pgrep -af "${WAI_QUANTUM_FPGA_FOREIGN:-kv_|flashv|flash8|ofpga|fpgautil|aba_|wca_}" | grep -v -e pgrep -e power_campaign -e run_all -e "${KNOWN_ANSWER:-^$}" || true; } +foreign() { pgrep -af "${WAI_QUANTUM_LANDSCAPE_FPGA_FOREIGN:-kv_|flashv|flash8|ofpga|fpgautil|aba_|wca_}" | grep -v -e pgrep -e power_campaign -e run_all -e "${KNOWN_ANSWER:-^$}" || true; } guard() { f=$(foreign) if [ -n "$f" ]; then log "STOP: another session is on the board: $f"; echo STOPPED > "$OUT/DONE"; exit 3; fi } -log "start; $(./wai-quantum-fpga status 2>&1 | tr '\n' ' ')" +log "start; $(./wai-quantum-landscape-fpga status 2>&1 | tr '\n' ' ')" guard -./wai-quantum-fpga meter t0-recall 1 0 300 > "$OUT/step.dat" 2>&1; log "step done" +./wai-quantum-landscape-fpga meter t0-recall 1 0 300 > "$OUT/step.dat" 2>&1; log "step done" if [ -n "${KNOWN_ANSWER:-}" ] && [ -n "${KNOWN_BITSTREAM:-}" ]; then guard sh "$KNOWN_ANSWER" "$KNOWN_BITSTREAM" 60 8 30 > "$OUT/known_answer.dat" 2>&1; log "known answer done exit=$?" [ -d /sys/class/gpio/gpio594 ] && echo 594 > /sys/class/gpio/unexport - ./wai-quantum-fpga load "$BIT" >> "$OUT/campaign.log" 2>&1 || { log "reload of the engine failed"; echo FAILED > "$OUT/DONE"; exit 2; } + ./wai-quantum-landscape-fpga load "$BIT" >> "$OUT/campaign.log" 2>&1 || { log "reload of the engine failed"; echo FAILED > "$OUT/DONE"; exit 2; } else log "known answer skipped: KNOWN_ANSWER and KNOWN_BITSTREAM not set" fi @@ -32,11 +33,11 @@ for spec in "null:t0-recall 8 60 30 --null --predict 0" "t0-recall:t0-recall 10 "gibbs-recall:gibbs-recall 10 60 30 --predict 12" "t0-idle:t0-idle 8 60 30 --predict 4"; do name=${spec%%:*}; args=${spec#*:} guard - ./wai-quantum-fpga meter $args > "$OUT/$name.dat" 2>&1; log "$name done exit=$?" + ./wai-quantum-landscape-fpga meter $args > "$OUT/$name.dat" 2>&1; log "$name done exit=$?" done guard -./wai-quantum-fpga clock 30 >> "$OUT/campaign.log" 2>&1 -./wai-quantum-fpga meter t0-recall 6 60 30 --predict 80 > "$OUT/t0-recall-50MHz.dat" 2>&1; log "50 MHz done exit=$?" -./wai-quantum-fpga clock 15 >> "$OUT/campaign.log" 2>&1 +./wai-quantum-landscape-fpga clock 30 >> "$OUT/campaign.log" 2>&1 +./wai-quantum-landscape-fpga meter t0-recall 6 60 30 --predict 80 > "$OUT/t0-recall-50MHz.dat" 2>&1; log "50 MHz done exit=$?" +./wai-quantum-landscape-fpga clock 15 >> "$OUT/campaign.log" 2>&1 log "end" echo DONE > "$OUT/DONE" diff --git a/wai/wai-quantum-fpga/scripts/power_tables.py b/wai/wai-quantum-landscape-fpga/scripts/power_tables.py similarity index 97% rename from wai/wai-quantum-fpga/scripts/power_tables.py rename to wai/wai-quantum-landscape-fpga/scripts/power_tables.py index ac34a5c42..78b23a1ed 100644 --- a/wai/wai-quantum-fpga/scripts/power_tables.py +++ b/wai/wai-quantum-landscape-fpga/scripts/power_tables.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 """Per-class table from meter logs, by the pre-registered statistic (mean of per-pass differences of medians, SE across passes), plus a sensitivity column that was NOT pre-registered: the same with a 10%-trimmed mean inside each point, which keeps sub-quantum information a median of diff --git a/wai/wai-quantum-fpga/scripts/run_all.sh b/wai/wai-quantum-landscape-fpga/scripts/run_all.sh similarity index 57% rename from wai/wai-quantum-fpga/scripts/run_all.sh rename to wai/wai-quantum-landscape-fpga/scripts/run_all.sh index f8e42a31a..e805de60a 100755 --- a/wai/wai-quantum-fpga/scripts/run_all.sh +++ b/wai/wai-quantum-landscape-fpga/scripts/run_all.sh @@ -1,7 +1,8 @@ #!/bin/sh +# SPDX-License-Identifier: Apache-2.0 # Everything a builder runs on their own board, in order: load the engine, the conformance set, the # worked examples against the CPU reference, throughput, then the power campaign. On the board, as -# root, from a directory holding wai-quantum-fpga, landscape.bit.bin, this script and +# root, from a directory holding wai-quantum-landscape-fpga, landscape.bit.bin, this script and # power_campaign.sh: # sudo setsid nohup sh run_all.sh > /dev/null 2>&1 & # and leave the board alone until results-*/power/DONE appears (about three hours). It refuses to @@ -9,7 +10,7 @@ cd "$(dirname "$0")" OUT=results-$(date -u +%Y%m%dT%H%M%SZ) mkdir -p "$OUT" -FOREIGN=$(pgrep -af "${WAI_QUANTUM_FPGA_FOREIGN:-kv_|flashv|flash8|ofpga|fpgautil|aba_|wca_}" | grep -v -e pgrep -e run_all -e power_campaign || true) +FOREIGN=$(pgrep -af "${WAI_QUANTUM_LANDSCAPE_FPGA_FOREIGN:-kv_|flashv|flash8|ofpga|fpgautil|aba_|wca_}" | grep -v -e pgrep -e run_all -e power_campaign || true) [ -z "$FOREIGN" ] || { echo "another session is on the board: $FOREIGN" > "$OUT/STOPPED"; exit 3; } { echo "fpga manager flags: $(cat /sys/class/fpga_manager/fpga0/flags)" @@ -18,11 +19,11 @@ FOREIGN=$(pgrep -af "${WAI_QUANTUM_FPGA_FOREIGN:-kv_|flashv|flash8|ofpga|fpgauti # A non-zero flags word makes the manager treat a plain bitstream as something else; it is put back. FLAGS=$(cat /sys/class/fpga_manager/fpga0/flags) echo 0 > /sys/class/fpga_manager/fpga0/flags -./wai-quantum-fpga load "${ENGINE_BITSTREAM:-landscape.bit.bin}" > "$OUT/load.txt" 2>&1 || { echo "$FLAGS" > /sys/class/fpga_manager/fpga0/flags; exit 2; } -./wai-quantum-fpga status > "$OUT/status.txt" 2>&1 -./wai-quantum-fpga conform > "$OUT/conform.txt" 2>&1 -./wai-quantum-fpga agree --seeds 200 > "$OUT/agree.txt" 2>&1 -./wai-quantum-fpga throughput > "$OUT/throughput.txt" 2>&1 +./wai-quantum-landscape-fpga load "${ENGINE_BITSTREAM:-landscape.bit.bin}" > "$OUT/load.txt" 2>&1 || { echo "$FLAGS" > /sys/class/fpga_manager/fpga0/flags; exit 2; } +./wai-quantum-landscape-fpga status > "$OUT/status.txt" 2>&1 +./wai-quantum-landscape-fpga conform > "$OUT/conform.txt" 2>&1 +./wai-quantum-landscape-fpga agree --seeds 200 > "$OUT/agree.txt" 2>&1 +./wai-quantum-landscape-fpga throughput > "$OUT/throughput.txt" 2>&1 sh power_campaign.sh "$OUT" -./wai-quantum-fpga release >> "$OUT/status.txt" 2>&1 +./wai-quantum-landscape-fpga release >> "$OUT/status.txt" 2>&1 echo "$FLAGS" > /sys/class/fpga_manager/fpga0/flags diff --git a/wai/wai-quantum-fpga/scripts/step_settle.py b/wai/wai-quantum-landscape-fpga/scripts/step_settle.py old mode 100755 new mode 100644 similarity index 96% rename from wai/wai-quantum-fpga/scripts/step_settle.py rename to wai/wai-quantum-landscape-fpga/scripts/step_settle.py index adafcfac3..4b9ab579d --- a/wai/wai-quantum-fpga/scripts/step_settle.py +++ b/wai/wai-quantum-landscape-fpga/scripts/step_settle.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# SPDX-License-Identifier: Apache-2.0 """Settle time of the run/halt switch, from `vqpu-edge meter 1 0 300` (no settle, 300 samples). LAST deviation, not first touch: the latest sample that sits outside a band around the segment's diff --git a/wai/wai-quantum-fpga/src/board.rs b/wai/wai-quantum-landscape-fpga/src/board.rs similarity index 93% rename from wai/wai-quantum-fpga/src/board.rs rename to wai/wai-quantum-landscape-fpga/src/board.rs index 262b39a7f..3690da79f 100644 --- a/wai/wai-quantum-fpga/src/board.rs +++ b/wai/wai-quantum-landscape-fpga/src/board.rs @@ -21,11 +21,11 @@ pub fn dmesg_fpga_writes() -> Result, Error> { /// Processes that, by the board's written rules, mean another session is flashing or measuring. pub fn foreign_sessions() -> String { - // Override the patterns for another board's habits: WAI_QUANTUM_FPGA_FOREIGN, an extended regex. - let pat = std::env::var("WAI_QUANTUM_FPGA_FOREIGN").unwrap_or_else(|_| "kv_|flashv|flash8|flashfull|ofpga|fpgautil|aba_|wca_".into()); + // Override the patterns for another board's habits: WAI_QUANTUM_LANDSCAPE_FPGA_FOREIGN, an extended regex. + let pat = std::env::var("WAI_QUANTUM_LANDSCAPE_FPGA_FOREIGN").unwrap_or_else(|_| "kv_|flashv|flash8|flashfull|ofpga|fpgautil|aba_|wca_".into()); let out = Command::new("sh") .arg("-c") - .arg(format!("pgrep -af '{pat}' | grep -v -e pgrep -e wai-quantum-fpga || true")) + .arg(format!("pgrep -af '{pat}' | grep -v -e pgrep -e wai-quantum-landscape-fpga || true")) .output(); out.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()).unwrap_or_default() } @@ -110,8 +110,8 @@ unsafe extern "C" { pub struct Lock(#[allow(dead_code)] fs::File); pub fn lock() -> Result { use std::os::unix::io::AsRawFd; - let f = fs::OpenOptions::new().create(true).write(true).truncate(false).open("/run/lock/wai-quantum-fpga.lock") - .or_else(|_| fs::OpenOptions::new().create(true).write(true).truncate(false).open("/tmp/wai-quantum-fpga.lock")) + let f = fs::OpenOptions::new().create(true).write(true).truncate(false).open("/run/lock/wai-quantum-landscape-fpga.lock") + .or_else(|_| fs::OpenOptions::new().create(true).write(true).truncate(false).open("/tmp/wai-quantum-landscape-fpga.lock")) .map_err(|e| Error(format!("lock file: {e}")))?; if unsafe { flock(f.as_raw_fd(), 2) } != 0 { return err("flock failed"); diff --git a/wai/wai-quantum-fpga/src/conform.rs b/wai/wai-quantum-landscape-fpga/src/conform.rs similarity index 100% rename from wai/wai-quantum-fpga/src/conform.rs rename to wai/wai-quantum-landscape-fpga/src/conform.rs diff --git a/wai/wai-quantum-fpga/src/emio.rs b/wai/wai-quantum-landscape-fpga/src/emio.rs similarity index 100% rename from wai/wai-quantum-fpga/src/emio.rs rename to wai/wai-quantum-landscape-fpga/src/emio.rs diff --git a/wai/wai-quantum-fpga/src/engine.rs b/wai/wai-quantum-landscape-fpga/src/engine.rs similarity index 100% rename from wai/wai-quantum-fpga/src/engine.rs rename to wai/wai-quantum-landscape-fpga/src/engine.rs diff --git a/wai/wai-quantum-fpga/src/lib.rs b/wai/wai-quantum-landscape-fpga/src/lib.rs similarity index 100% rename from wai/wai-quantum-fpga/src/lib.rs rename to wai/wai-quantum-landscape-fpga/src/lib.rs diff --git a/wai/wai-quantum-fpga/src/main.rs b/wai/wai-quantum-landscape-fpga/src/main.rs similarity index 84% rename from wai/wai-quantum-fpga/src/main.rs rename to wai/wai-quantum-landscape-fpga/src/main.rs index d7c7b8a86..b15e1bd17 100644 --- a/wai/wai-quantum-fpga/src/main.rs +++ b/wai/wai-quantum-landscape-fpga/src/main.rs @@ -1,4 +1,4 @@ -//! wai-quantum-fpga: the command line. Board commands run on the board's own Linux, as root (for +//! wai-quantum-landscape-fpga: the command line. Board commands run on the board's own Linux, as root (for //! /dev/mem). `selftest`, `gen-tb` and `analyze` run anywhere. use serde_json::Value; @@ -6,17 +6,17 @@ use std::fmt::Write as _; use std::process::exit; use wai_quantum::quantum_landscape::energy::Landscape; use wai_quantum::quantum_landscape::job; -use wai_quantum_fpga::conform::{self, Report}; -use wai_quantum_fpga::engine::{Engine, Loaded}; -use wai_quantum_fpga::member::{BoardInfo, BoardMember, lowered, plan}; -use wai_quantum_fpga::model::ModelPort; -use wai_quantum_fpga::port::{Error, Port}; -use wai_quantum_fpga::regs::*; -use wai_quantum_fpga::script::ScriptPort; +use wai_quantum_landscape_fpga::conform::{self, Report}; +use wai_quantum_landscape_fpga::engine::{Engine, Loaded}; +use wai_quantum_landscape_fpga::member::{BoardInfo, BoardMember, lowered, plan}; +use wai_quantum_landscape_fpga::model::ModelPort; +use wai_quantum_landscape_fpga::port::{Error, Port}; +use wai_quantum_landscape_fpga::regs::*; +use wai_quantum_landscape_fpga::script::ScriptPort; fn usage() -> ! { eprintln!( - "usage: wai-quantum-fpga \n\ + "usage: wai-quantum-landscape-fpga \n\ \x20 selftest [--scale X] [--seeds N] every check against the register-level model (no board)\n\ \x20 gen-tb the RTL testbench's script, recorded from the model\n\ \x20 analyze [--predict mW] a power log, summarised\n\ @@ -42,7 +42,7 @@ fn flag(args: &[String], name: &str) -> Option { } fn die(e: impl std::fmt::Display) -> ! { - eprintln!("wai-quantum-fpga: {e}"); + eprintln!("wai-quantum-landscape-fpga: {e}"); exit(1) } @@ -209,7 +209,7 @@ fn on_board(cmd: &str, args: &[String], port: P) { } #[cfg(unix)] -fn meter_class(e: &mut Engine

, class: &str, plan_m: &wai_quantum_fpga::power::Plan, log: &mut String) -> Vec { +fn meter_class(e: &mut Engine

, class: &str, plan_m: &wai_quantum_landscape_fpga::power::Plan, log: &mut String) -> Vec { let (l, cue) = recall(); let lw = lowered(&l.lower()); let seg = plan(&lw, &[0.25]).unwrap_or_else(|x| die(x)).remove(0); @@ -236,7 +236,7 @@ fn meter_class(e: &mut Engine

, class: &str, plan_m: &wai_quantum_fpg other => Err(Error(format!("unknown class {other}"))), } }; - wai_quantum_fpga::power::meter(e, plan_m, &mut start, log).unwrap_or_else(|x| die(x)) + wai_quantum_landscape_fpga::power::meter(e, plan_m, &mut start, log).unwrap_or_else(|x| die(x)) } fn main() { @@ -267,31 +267,31 @@ fn main() { let text = std::fs::read_to_string(f).unwrap_or_else(|e| die(e)); #[cfg(unix)] { - let pts = wai_quantum_fpga::power::parse_log(&text); - print!("{}", wai_quantum_fpga::power::summarise(&pts, flag(&args, "--predict")).text); + let pts = wai_quantum_landscape_fpga::power::parse_log(&text); + print!("{}", wai_quantum_landscape_fpga::power::summarise(&pts, flag(&args, "--predict")).text); } let _ = text; } #[cfg(unix)] "load" => { let f = args.get(1).unwrap_or_else(|| usage()); - println!("{}", wai_quantum_fpga::board::load_bitstream(f).unwrap_or_else(|e| die(e))); - let _l = wai_quantum_fpga::board::lock().unwrap_or_else(|e| die(e)); - let port = wai_quantum_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e)); + println!("{}", wai_quantum_landscape_fpga::board::load_bitstream(f).unwrap_or_else(|e| die(e))); + let _l = wai_quantum_landscape_fpga::board::lock().unwrap_or_else(|e| die(e)); + let port = wai_quantum_landscape_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e)); Engine::open(port).unwrap_or_else(|e| die(e)); println!("the engine answers with its ID and version"); } #[cfg(unix)] "release" => { - wai_quantum_fpga::emio::EmioPort::release_to_boot_state().unwrap_or_else(|e| die(e)); + wai_quantum_landscape_fpga::emio::EmioPort::release_to_boot_state().unwrap_or_else(|e| die(e)); println!("EMIO banks 3-5: inputs, outputs disabled, data 0"); } #[cfg(unix)] "clock" => { let d: u32 = args.get(1).and_then(|v| v.parse().ok()).unwrap_or_else(|| usage()); - let _l = wai_quantum_fpga::board::lock().unwrap_or_else(|e| die(e)); - let mut e = Engine::open(wai_quantum_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e))).unwrap_or_else(|e| die(e)); - let old = wai_quantum_fpga::board::set_pl0_div(d).unwrap_or_else(|e| die(e)); + let _l = wai_quantum_landscape_fpga::board::lock().unwrap_or_else(|e| die(e)); + let mut e = Engine::open(wai_quantum_landscape_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e))).unwrap_or_else(|e| die(e)); + let old = wai_quantum_landscape_fpga::board::set_pl0_div(d).unwrap_or_else(|e| die(e)); std::thread::sleep(std::time::Duration::from_millis(100)); let t0 = std::time::Instant::now(); let u0 = e.counter(UPTIME).unwrap_or_else(|e| die(e)); @@ -305,30 +305,30 @@ fn main() { let passes: u32 = args.get(2).and_then(|v| v.parse().ok()).unwrap_or_else(|| usage()); let settle_s: u64 = args.get(3).and_then(|v| v.parse().ok()).unwrap_or_else(|| usage()); let samples: u32 = args.get(4).and_then(|v| v.parse().ok()).unwrap_or_else(|| usage()); - let plan_m = wai_quantum_fpga::power::Plan { passes, settle_s, samples, null: args.iter().any(|a| a == "--null") }; + let plan_m = wai_quantum_landscape_fpga::power::Plan { passes, settle_s, samples, null: args.iter().any(|a| a == "--null") }; let cores: u16 = flag(&args, "--cores").unwrap_or(1); - let _l = wai_quantum_fpga::board::lock().unwrap_or_else(|e| die(e)); - let port = wai_quantum_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e)); + let _l = wai_quantum_landscape_fpga::board::lock().unwrap_or_else(|e| die(e)); + let port = wai_quantum_landscape_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e)); let mut log = String::new(); let _ = writeln!(log, "# class {class} cores {cores}"); let pts = if cores > 1 { - let mut e = Engine::open(wai_quantum_fpga::port::BroadcastPort { inner: port, k: cores }).unwrap_or_else(|x| die(x)); + let mut e = Engine::open(wai_quantum_landscape_fpga::port::BroadcastPort { inner: port, k: cores }).unwrap_or_else(|x| die(x)); meter_class(&mut e, &class, &plan_m, &mut log) } else { let mut e = Engine::open(port).unwrap_or_else(|x| die(x)); meter_class(&mut e, &class, &plan_m, &mut log) }; print!("{log}"); - for l in wai_quantum_fpga::power::summarise(&pts, flag(&args, "--predict")).text.lines() { + for l in wai_quantum_landscape_fpga::power::summarise(&pts, flag(&args, "--predict")).text.lines() { println!("# {l}"); } } #[cfg(unix)] "conform" | "agree" | "landscape" | "status" | "throughput" => { - let _l = wai_quantum_fpga::board::lock().unwrap_or_else(|e| die(e)); - let mut port = wai_quantum_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e)); + let _l = wai_quantum_landscape_fpga::board::lock().unwrap_or_else(|e| die(e)); + let mut port = wai_quantum_landscape_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e)); match flag::(&args, "--core") { - Some(core) => on_board(cmd, &args, wai_quantum_fpga::port::CorePort { inner: &mut port, core }), + Some(core) => on_board(cmd, &args, wai_quantum_landscape_fpga::port::CorePort { inner: &mut port, core }), None => on_board(cmd, &args, port), } } diff --git a/wai/wai-quantum-fpga/src/member.rs b/wai/wai-quantum-landscape-fpga/src/member.rs similarity index 100% rename from wai/wai-quantum-fpga/src/member.rs rename to wai/wai-quantum-landscape-fpga/src/member.rs diff --git a/wai/wai-quantum-fpga/src/model.rs b/wai/wai-quantum-landscape-fpga/src/model.rs similarity index 100% rename from wai/wai-quantum-fpga/src/model.rs rename to wai/wai-quantum-landscape-fpga/src/model.rs diff --git a/wai/wai-quantum-fpga/src/port.rs b/wai/wai-quantum-landscape-fpga/src/port.rs similarity index 100% rename from wai/wai-quantum-fpga/src/port.rs rename to wai/wai-quantum-landscape-fpga/src/port.rs diff --git a/wai/wai-quantum-fpga/src/power.rs b/wai/wai-quantum-landscape-fpga/src/power.rs similarity index 98% rename from wai/wai-quantum-fpga/src/power.rs rename to wai/wai-quantum-landscape-fpga/src/power.rs index ec8c71c57..82936f241 100644 --- a/wai/wai-quantum-fpga/src/power.rs +++ b/wai/wai-quantum-landscape-fpga/src/power.rs @@ -50,7 +50,7 @@ fn urandom_bit() -> bool { pub fn meter(edge: &mut Engine

, plan: &Plan, start: &mut dyn FnMut(&mut Engine

) -> Result<(), Error>, log: &mut String) -> Result, Error> { let sensor = board::find_power_sensor()?; let mut pts = vec![]; - let _ = writeln!(log, "# wai-quantum-fpga meter passes={} settle={}s samples={} null={} sensor={sensor} started={:?}", plan.passes, plan.settle_s, plan.samples, plan.null, SystemTime::now()); + let _ = writeln!(log, "# wai-quantum-landscape-fpga meter passes={} settle={}s samples={} null={} sensor={sensor} started={:?}", plan.passes, plan.settle_s, plan.samples, plan.null, SystemTime::now()); let _ = writeln!(log, "# pass state sample power_uW"); for pass in 1..=plan.passes { let order = if urandom_bit() { [true, false] } else { [false, true] }; diff --git a/wai/wai-quantum-fpga/src/regs.rs b/wai/wai-quantum-landscape-fpga/src/regs.rs similarity index 100% rename from wai/wai-quantum-fpga/src/regs.rs rename to wai/wai-quantum-landscape-fpga/src/regs.rs diff --git a/wai/wai-quantum-fpga/src/script.rs b/wai/wai-quantum-landscape-fpga/src/script.rs similarity index 100% rename from wai/wai-quantum-fpga/src/script.rs rename to wai/wai-quantum-landscape-fpga/src/script.rs diff --git a/wai/wai-quantum-fpga/tb/tb_landscape.v b/wai/wai-quantum-landscape-fpga/tb/tb_landscape.v similarity index 98% rename from wai/wai-quantum-fpga/tb/tb_landscape.v rename to wai/wai-quantum-landscape-fpga/tb/tb_landscape.v index 33cdd8911..6f94561d9 100644 --- a/wai/wai-quantum-fpga/tb/tb_landscape.v +++ b/wai/wai-quantum-landscape-fpga/tb/tb_landscape.v @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 // Replays a session recorded from the host's software model (`vqpu-edge gen-tb`) against the RTL, // through the EMIO port and its synchroniser, with the PS side deliberately off the PL clock. // Every read must return what the model returned. A mismatch names the script line. -- 2.54.0 (Apple Git-157) From fcf5fb66c4040a2a77008a8d9c93dde208e2f93c Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Fri, 6 Nov 2026 08:06:57 -0500 Subject: [PATCH 03/11] wai-quantum-landscape-fpga: pre-register the joint-statistics test and the AXI build's power check Both written before either has run on the board. Co-Authored-By: Claude Opus 5.5 --- .../2026-11-06/AXI-PREDICTION.txt | 10 +++++ .../measurements/2026-11-06/JOINT-PREREG.txt | 38 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/AXI-PREDICTION.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/JOINT-PREREG.txt diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/AXI-PREDICTION.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/AXI-PREDICTION.txt new file mode 100644 index 000000000..5993b17f4 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/AXI-PREDICTION.txt @@ -0,0 +1,10 @@ +Written 2026-11-06, before the AXI-link build was metered, and committed before the measurement. +Not edited after the data. + +The AXI-link build (rtl/landscape_axi.v) carries the same engine (rtl/landscape_core.v, rtl version 2) +as the EMIO build the class campaign measured (measurements/2026-11-06/PREDICTION.txt). The link is +idle while the meter reads: the run state is the engine in repeat mode with no processor access. + +PREDICTION: t0-recall run minus halt on the AXI build, 3 passes, 60 s settle, 30 samples, equals the +EMIO build's t0-recall class result within 2 combined standard errors. If it does not, the power +figures quoted for the design are the AXI build's, and the class campaign is repeated on it. diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/JOINT-PREREG.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/JOINT-PREREG.txt new file mode 100644 index 000000000..9f6c49914 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/JOINT-PREREG.txt @@ -0,0 +1,38 @@ +Written 2026-11-06, before any board run of the joint test, and committed before it. Not edited +after the data. + +WHAT IS TESTED. The sample kernel's joint law, not only its per-cell frequencies: a sampler that drew +each cell from its right marginal, independently, would pass every per-cell test. Four cases, each a +workload small enough that the exact law of a chain's final state can be enumerated over all 3^n +states (src/exact.rs, `exact::cases`): + + ring 8, fixed T = 1.5, 4 sweeps 200,000 chains 6,561 states + ring 8, the job's anneal from T = 2.5, 10 sweeps 200,000 chains 6,561 states + dense 6, fixed T = 4, 2 sweeps 200,000 chains 729 states + sparse 10, the job's anneal from T = 1.5, 8 sweeps 400,000 chains 59,049 states + +Chain r of a case has the seed chain_seed(2027, r). (Seed 2026 was used while writing the test, on +the model; 2027 has not been run anywhere before this file was committed.) Every chain runs on the +board through the AXI link's sequencer, and on the CPU reference. + +THE EXACT LAW. A probability vector over every state, pushed through each cell update of each sweep +with the three probabilities a/2^53, (b-a)/2^53 and 1 - b/2^53 from the reference's cut points +(a, b) = cuts(h, cost, beta), then through the synchronous sweep at temperature 0 where the job's +anneal ends with one. It assumes ideal uniform 53-bit draws; nothing is sampled. + +STATISTICS AND THE RULE, per case: + 1. Pearson chi-square and the G-test over every state expected at least 5 times, the rest pooled + into one bin: p >= 0.001 for both. + 2. Every cell's three marginal frequencies (z scores) and every pair of cells' nine joint + frequencies: Bonferroni p >= 0.001 for each family. + 3. Every chain's final state on the board equals the CPU reference's. + 4. Control: the same histogram against the law with the exact marginals and independent cells + must be rejected, chi-square p < 1e-6, while its cell family still passes. This shows the test + sees the joint structure that per-cell statistics cannot. +A case passes if 1-4 hold. Sixteen p values are taken at 0.001 each; the chance that a correct +kernel fails one by chance is about 1.6%. + +WHAT IT DOES NOT SHOW. Because the board is bit-exact (rule 3), its histogram is the reference's: +the test holds the shared kernel (the reference's algorithm and its counter-based draws) to exact +enumeration on the board's own outputs; it is not an independent sample of a different machine. +Board-level, one board. -- 2.54.0 (Apple Git-157) From 4f7baba760457e6b56f1b315d68652599edc285b Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Fri, 6 Nov 2026 08:16:09 -0500 Subject: [PATCH 04/11] wai-quantum-landscape-fpga: an AXI link, and the sample kernel held to its exact joint law The engine gets a second link to the processor (rtl/landscape_axi.v): its registers on an AXI-Lite port, bulk loads (couplings, fields, cut-point tables, chain starts) as write lists the link reads from DDR by DMA, and a sequencer that runs a batch of up to 256 chains in fabric, start to result, with nothing from the processor between them. The link answers every access within a few clocks whatever the engine is doing, and the host reads a signature over EMIO before every AXI access, so a design replaced by another session is caught before an access could go unanswered. The host runs a job's chains as one batch, segment by segment, every chain starting a segment with its draw counter at the draw the reference takes next. A testbench drives the link over AXI-Lite and its DMA from a memory model and checks every read against the register-level model. src/exact.rs holds the sample kernel to the exact law of the whole state, enumerated over every state of small workloads, not only to per-cell frequencies, with a control that has the right marginals and independent cells and must be rejected. src/latency.rs times the processor-to-logic accesses on the board. The session check no longer mistakes its own parent process for another session. Co-Authored-By: Claude Opus 5.5 --- .../workflows/wai-quantum-landscape-fpga.yml | 16 +- wai/wai-quantum-landscape-fpga/README.md | 9 +- wai/wai-quantum-landscape-fpga/landscape.tcl | 42 +- .../rtl/landscape_axi.v | 271 ++++++++++ .../rtl/landscape_top_axi.v | 58 +++ .../scripts/build_bitstream.sh | 7 +- .../scripts/run_all.sh | 8 +- wai/wai-quantum-landscape-fpga/src/axi.rs | 389 +++++++++++++++ wai/wai-quantum-landscape-fpga/src/board.rs | 24 +- wai/wai-quantum-landscape-fpga/src/emio.rs | 4 + wai/wai-quantum-landscape-fpga/src/engine.rs | 56 ++- wai/wai-quantum-landscape-fpga/src/exact.rs | 464 ++++++++++++++++++ wai/wai-quantum-landscape-fpga/src/latency.rs | 198 ++++++++ wai/wai-quantum-landscape-fpga/src/lib.rs | 3 + wai/wai-quantum-landscape-fpga/src/main.rs | 128 ++++- wai/wai-quantum-landscape-fpga/src/member.rs | 102 ++-- wai/wai-quantum-landscape-fpga/src/port.rs | 63 ++- wai/wai-quantum-landscape-fpga/src/script.rs | 63 ++- .../tb/tb_landscape_axi.v | 215 ++++++++ 19 files changed, 2011 insertions(+), 109 deletions(-) create mode 100644 wai/wai-quantum-landscape-fpga/rtl/landscape_axi.v create mode 100644 wai/wai-quantum-landscape-fpga/rtl/landscape_top_axi.v create mode 100644 wai/wai-quantum-landscape-fpga/src/axi.rs create mode 100644 wai/wai-quantum-landscape-fpga/src/exact.rs create mode 100644 wai/wai-quantum-landscape-fpga/src/latency.rs create mode 100644 wai/wai-quantum-landscape-fpga/tb/tb_landscape_axi.v diff --git a/.github/workflows/wai-quantum-landscape-fpga.yml b/.github/workflows/wai-quantum-landscape-fpga.yml index d5e3a6988..335e10d05 100644 --- a/.github/workflows/wai-quantum-landscape-fpga.yml +++ b/.github/workflows/wai-quantum-landscape-fpga.yml @@ -1,8 +1,8 @@ name: wai-quantum-landscape-fpga reference design # The FPGA board's member of energy-landscape jobs (wai/wai-quantum-landscape-fpga): its unit tests, and every -# check of the conformance set and every worked example run against the register-level model of the -# logic (src/model.rs), which the logic's testbench is held to. No board and no vendor tool here: the +# check of the conformance set, every worked example and the joint-statistics test run against the +# register-level model of the logic (src/model.rs), which the logic's testbenches are held to. No board and no vendor tool here: the # board's own runs are in the crate's measurements and README. concurrency: @@ -58,10 +58,20 @@ jobs: cargo run --locked --release -- selftest --scale 0.05 --seeds 5 | tee "$RUNNER_TEMP/selftest.log" grep -q 'sample kernel: the chain vectors, bit for bit' "$RUNNER_TEMP/selftest.log" grep -qE '^[0-9]+ checks, 0 failed$' "$RUNNER_TEMP/selftest.log" - - name: The testbench's script records from the model + - name: Joint statistics against exact enumeration, on the model + shell: bash + working-directory: wai/wai-quantum-landscape-fpga + run: | + set -euo pipefail + cargo run --locked --release -- joint --model --scale 0.05 | tee "$RUNNER_TEMP/joint.log" + grep -qE '^[0-9]+ checks, 0 failed$' "$RUNNER_TEMP/joint.log" + - name: The testbenches' scripts record from the model shell: bash working-directory: wai/wai-quantum-landscape-fpga run: | set -euo pipefail cargo run --locked --release -- gen-tb "$RUNNER_TEMP/landscape_tb.script" test "$(grep -c '^R ' "$RUNNER_TEMP/landscape_tb.script")" -gt 200 + cargo run --locked --release -- gen-tb "$RUNNER_TEMP/landscape_axi_tb.script" --axi + test "$(grep -c '^Q ' "$RUNNER_TEMP/landscape_axi_tb.script")" -gt 5 + test "$(grep -c '^D ' "$RUNNER_TEMP/landscape_axi_tb.script")" -gt 10 diff --git a/wai/wai-quantum-landscape-fpga/README.md b/wai/wai-quantum-landscape-fpga/README.md index 5cb9e151b..5e2fdbf4d 100644 --- a/wai/wai-quantum-landscape-fpga/README.md +++ b/wai/wai-quantum-landscape-fpga/README.md @@ -176,11 +176,10 @@ with its couplings fixed in the logic. 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. -- **The four-core build** of the first version fit (97,932 LUTs, 98% of slices, timing met); this - version's core is larger (below) and four of it were not built. The host addresses one core at a - time or all at once, and does not schedule different chains on different cores. -- **The job port is slow.** At 1.6 µs a register access, a full table level is 128 × 4 writes; a job - with long schedules on wide windows spends most of its time writing the table. +- **Four cores fit, 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), and does not schedule different chains on different cores. +- **The job port is slow.** At 1.6 µs a register access, each table entry is four writes; a job with + long schedules over wide windows spends most of its time writing the table, not computing. - **A cell update takes four clocks**, and in sequential mode one of 64 cones is used at a time. ## Files diff --git a/wai/wai-quantum-landscape-fpga/landscape.tcl b/wai/wai-quantum-landscape-fpga/landscape.tcl index 953395ef4..0eb347637 100644 --- a/wai/wai-quantum-landscape-fpga/landscape.tcl +++ b/wai/wai-quantum-landscape-fpga/landscape.tcl @@ -1,8 +1,11 @@ # SPDX-License-Identifier: Apache-2.0 # Bitstream build for the energy-landscape engine. Vivado batch mode: -# vivado -mode batch -source landscape.tcl -tclargs [FREQ ] [TAG ] [JOBS ] [CORES ] -# The part and board are data: PART and BOARD below. The processing system provides the clock and the -# EMIO lines; there is no other programmable-logic I/O. +# vivado -mode batch -source landscape.tcl -tclargs [FREQ ] [TAG ] [JOBS ] [CORES ] [LINK emio|axi] +# The part and board are data: PART and BOARD below. There is no programmable-logic I/O: the +# processing system provides the clock and the link. LINK emio (the default) carries register +# accesses over the EMIO GPIO lines; LINK axi gives an AXI-Lite register port, a write-list DMA +# from DDR and an in-fabric chain sequencer (rtl/landscape_axi.v). Every block below is the +# vendor's no-charge processing-system configuration or AXI infrastructure; no licensed IP. set part xck26-sfvc784-2LV-c set board xilinx.com:kv260_som:part0:2.0 proc arg {name dflt} { @@ -15,8 +18,9 @@ set freq [arg FREQ 100] set tag [arg TAG landscape] set jobs [arg JOBS 4] set cores [arg CORES 1] +set link [arg LINK emio] set here [file dirname [file normalize [info script]]] -puts "BUILD: tag=$tag freq=$freq jobs=$jobs cores=$cores here=$here" +puts "BUILD: tag=$tag freq=$freq jobs=$jobs cores=$cores link=$link here=$here" create_project ev_$tag ./ev_$tag -part $part -force set_property board_part $board [current_project] @@ -36,6 +40,34 @@ connect_bd_net [get_bd_pins ps/emio_gpio_o] [get_bd_ports emio_o] connect_bd_net [get_bd_pins ps/emio_gpio_i] [get_bd_ports emio_i] create_bd_port -dir O -type clk pl_clk0 connect_bd_net [get_bd_pins ps/pl_clk0] [get_bd_ports pl_clk0] +if {$link eq "axi"} { + # M_AXI_HPM0_LPD (32-bit) -> protocol converter -> LITE at 0x8000_0000, 256 KiB. + # DMA (AXI4, 64-bit, read only) -> S_AXI_HP0_FPD -> DDR. The reset is the fabric's own (aresetn). + set_property -dict [list CONFIG.PSU__USE__M_AXI_GP2 {1} CONFIG.PSU__MAXIGP2__DATA_WIDTH {32} \ + CONFIG.PSU__USE__S_AXI_GP2 {1} CONFIG.PSU__SAXIGP2__DATA_WIDTH {64}] [get_bd_cells ps] + connect_bd_net [get_bd_pins ps/pl_clk0] [get_bd_pins ps/maxihpm0_lpd_aclk] + connect_bd_net [get_bd_pins ps/pl_clk0] [get_bd_pins ps/saxihp0_fpd_aclk] + create_bd_port -dir I -type rst aresetn + create_bd_cell -type ip -vlnv [lindex [get_ipdefs *axi_protocol_converter*] end] conv + set_property -dict [list CONFIG.SI_PROTOCOL {AXI4} CONFIG.MI_PROTOCOL {AXI4LITE} CONFIG.DATA_WIDTH {32}] [get_bd_cells conv] + connect_bd_net [get_bd_pins ps/pl_clk0] [get_bd_pins conv/aclk] + connect_bd_net [get_bd_ports aresetn] [get_bd_pins conv/aresetn] + connect_bd_intf_net [get_bd_intf_pins ps/M_AXI_HPM0_LPD] [get_bd_intf_pins conv/S_AXI] + create_bd_intf_port -mode Master -vlnv xilinx.com:interface:aximm_rtl:1.0 LITE + set_property -dict [list CONFIG.PROTOCOL {AXI4LITE} CONFIG.ADDR_WIDTH {32} CONFIG.DATA_WIDTH {32}] [get_bd_intf_ports LITE] + connect_bd_intf_net [get_bd_intf_pins conv/M_AXI] [get_bd_intf_ports LITE] + create_bd_intf_port -mode Slave -vlnv xilinx.com:interface:aximm_rtl:1.0 DMA + set_property -dict [list CONFIG.PROTOCOL {AXI4} CONFIG.ADDR_WIDTH {49} CONFIG.DATA_WIDTH {64} CONFIG.ID_WIDTH {6} \ + CONFIG.READ_WRITE_MODE {READ_ONLY} CONFIG.HAS_REGION {0} CONFIG.HAS_QOS {0} CONFIG.HAS_LOCK {0}] [get_bd_intf_ports DMA] + connect_bd_intf_net [get_bd_intf_ports DMA] [get_bd_intf_pins ps/S_AXI_HP0_FPD] + set_property CONFIG.ASSOCIATED_BUSIF {LITE:DMA} [get_bd_ports pl_clk0] + set_property CONFIG.ASSOCIATED_RESET {aresetn} [get_bd_ports pl_clk0] + assign_bd_address + set lite [get_bd_addr_segs -of_objects [get_bd_addr_spaces ps/Data] -filter {NAME =~ *LITE*}] + set_property offset 0x80000000 $lite + set_property range 256K $lite + foreach seg [get_bd_addr_segs -of_objects [get_bd_addr_spaces ps/Data]] { puts "ADDR $seg [get_property offset $seg] [get_property range $seg]" } +} validate_bd_design make_wrapper -files [get_files sys.bd] -top -import generate_target all [get_files sys.bd] @@ -43,6 +75,8 @@ generate_target all [get_files sys.bd] if {$cores > 1} { add_files [list $here/rtl/landscape_top_multi.v $here/rtl/landscape_multi.v $here/rtl/landscape_core.v] set_property verilog_define [list LANDSCAPE_K=$cores] [current_fileset] +} elseif {$link eq "axi"} { + add_files [list $here/rtl/landscape_top_axi.v $here/rtl/landscape_axi.v $here/rtl/landscape_core.v] } else { add_files [list $here/rtl/landscape_top.v $here/rtl/landscape_port.v $here/rtl/landscape_core.v] } diff --git a/wai/wai-quantum-landscape-fpga/rtl/landscape_axi.v b/wai/wai-quantum-landscape-fpga/rtl/landscape_axi.v new file mode 100644 index 000000000..5cf8d57d3 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/rtl/landscape_axi.v @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: Apache-2.0 +// The engine's link to the processor: an AXI-Lite register port, a write-list DMA from DDR, and a +// chain sequencer that runs a batch of chains in fabric. +// +// AXI-Lite (from the processor's low-power-domain master, through a protocol converter): word +// address = byte address >> 2. Words 0x0000-0x7FFF are the engine's own map (src/regs.rs); +// 0x8000 and up are this wrapper's (src/axi.rs). Every access is answered within a few clocks, +// whatever the engine, the DMA or the sequencer is doing: an access that never completes would +// hold the processor's bus, and on a shared, remote board nobody is there to power-cycle it. So +// an engine-map access while the DMA or the sequencer holds the engine's bus is not queued: a +// write is dropped, a read returns 0xDEADBEEF, and either counts in DROPS, which the host checks. +// +// Write-list DMA (an AXI4 read master into the processor's high-performance port): the host puts +// a list of 64-bit entries in DDR, {16'b0, addr[15:0], data[31:0]}, and the wrapper applies each +// as a register write, in order, one every two clocks (the engine needs a clock between a +// coupling-memory clear and the next write). An entry for the chain RAM (0x9000-0x97FF) is +// written there. The list must start on a 128-byte boundary and not cross a 4 KiB page: bursts +// are 16 beats of 8 bytes. A read error stops the applying (DMA_ERR) but the list is still +// drained, so DMA_BUSY always falls. +// +// Chain sequencer: for k in 0..SEQ_COUNT (at most 256), it writes chain k's six words from the +// chain RAM (BOUNDARY 0-3, X0_LO, X0_HI) into the engine, starts the job, waits for it, and copies +// STATE 0-3, STEPS and STATUS into the result RAM (0xA000 + 8k). The job's mode, bound, table and +// workload are what the host set before. The anneal loop runs in fabric; the processor only fills +// the chain RAM and reads the results. +// +// EMIO: eight input lines carry a constant signature (0xA5) that the host reads through the +// processor's GPIO controller before it touches an AXI address: a GPIO read cannot hang the bus, +// so a design replaced by another session's is caught before an AXI access could go unanswered. +`timescale 1ns/1ps +`default_nettype none + +module landscape_axi ( + input wire clk, + input wire rstn, + // AXI4-Lite slave + input wire [31:0] s_awaddr, + input wire s_awvalid, + output reg s_awready = 1'b0, + input wire [31:0] s_wdata, + input wire s_wvalid, + output reg s_wready = 1'b0, + output wire [1:0] s_bresp, + output reg s_bvalid = 1'b0, + input wire s_bready, + input wire [31:0] s_araddr, + input wire s_arvalid, + output reg s_arready = 1'b0, + output reg [31:0] s_rdata = 32'd0, + output wire [1:0] s_rresp, + output reg s_rvalid = 1'b0, + input wire s_rready, + // AXI4 read master (64-bit data) + output reg [48:0] m_araddr = 49'd0, + output wire [7:0] m_arlen, + output wire [2:0] m_arsize, + output wire [1:0] m_arburst, + output wire [3:0] m_arcache, + output wire [2:0] m_arprot, + output reg m_arvalid = 1'b0, + input wire m_arready, + input wire [63:0] m_rdata, + input wire [1:0] m_rresp, + input wire m_rlast, + input wire m_rvalid, + output wire m_rready, + // EMIO signature + output wire [94:0] emio_i +); + localparam [31:0] LAXI = 32'h4C41_5849; // "LAXI" + localparam [31:0] WRAP_VERSION = 32'h0000_0001; + assign s_bresp = 2'b00; + assign s_rresp = 2'b00; + assign emio_i = {87'd0, 8'hA5}; + + // ---- the engine's register bus ------------------------------------------------------------ + reg stb = 1'b0, we = 1'b0; + reg [15:0] addr = 16'd0; + reg [31:0] wdata = 32'd0; + wire [31:0] rdata; + wire busy, done; + landscape_core core (.clk(clk), .reg_stb(stb), .reg_we(we), .reg_addr(addr), .reg_wdata(wdata), + .reg_rdata(rdata), .busy_o(busy), .done_o(done)); + + // ---- wrapper state ------------------------------------------------------------------------ + reg [31:0] dma_lo = 32'd0, dma_hi = 32'd0, dma_count = 32'd0; + reg [31:0] dma_left = 32'd0, dma_reqs = 32'd0; // entries still to receive, to request + reg dma_busy = 1'b0, dma_err = 1'b0; + reg [31:0] dma_done_count = 32'd0, drops = 32'd0; + reg [8:0] seq_count = 9'd0; + reg [15:0] seq_k = 16'd0, seq_done_count = 16'd0; + reg seq_busy = 1'b0; + reg [3:0] seq_ph = 4'd0; + reg [2:0] seq_i = 3'd0; + reg [3:0] seq_wait = 4'd0; + wire bus_free = !dma_busy && !seq_busy; + + // ---- the chain RAM and the result RAM: 256 chains x 8 words each, one write port each ------- + reg [31:0] chain_ram [0:2047]; + reg [31:0] res_ram [0:2047]; + reg cr_we = 1'b0, rr_we = 1'b0; + reg [10:0] cr_wa = 11'd0, rr_wa = 11'd0; + reg [31:0] cr_wd = 32'd0, rr_wd = 32'd0; + reg [31:0] chain_q = 32'd0, res_q = 32'd0; + reg [15:0] ar_word = 16'd0; + always @(posedge clk) begin + if (cr_we) chain_ram[cr_wa] <= cr_wd; + chain_q <= chain_ram[{seq_k[7:0], seq_i}]; + end + always @(posedge clk) begin + if (rr_we) res_ram[rr_wa] <= rr_wd; + res_q <= res_ram[ar_word[10:0]]; + end + + // ---- AXI-Lite capture --------------------------------------------------------------------- + reg aw_have = 1'b0, w_have = 1'b0, ar_have = 1'b0; + reg [15:0] aw_word = 16'd0; + reg [31:0] w_val = 32'd0; + reg [2:0] host_ph = 3'd0; // 0 idle, 2 engine read issued, 3 engine data next, 4 wrapper data, 5 result RAM data + reg [31:0] wrap_rdata = 32'd0; + + // ---- the DMA's read master ---------------------------------------------------------------- + assign m_arsize = 3'd3; // 8 bytes a beat + assign m_arburst = 2'b01; // INCR + assign m_arcache = 4'b0011; // normal, bufferable, not coherent: the host cleans the list to DDR + assign m_arprot = 3'b000; + reg [7:0] burst_len = 8'd0; + assign m_arlen = burst_len; + reg ent_have = 1'b0; // an entry waiting to be applied, on the next clock + reg [63:0] ent = 64'd0; + assign m_rready = !ent_have; + + always @(posedge clk) begin + stb <= 1'b0; + we <= 1'b0; + cr_we <= 1'b0; + rr_we <= 1'b0; + s_awready <= 1'b0; s_wready <= 1'b0; s_arready <= 1'b0; + if (!rstn) begin + dma_busy <= 1'b0; seq_busy <= 1'b0; m_arvalid <= 1'b0; ent_have <= 1'b0; + s_bvalid <= 1'b0; s_rvalid <= 1'b0; aw_have <= 1'b0; w_have <= 1'b0; ar_have <= 1'b0; host_ph <= 3'd0; + end else begin + // capture the host's access: an address and its data, or a read address + if (s_awvalid && !aw_have && !s_awready) begin s_awready <= 1'b1; aw_have <= 1'b1; aw_word <= s_awaddr[17:2]; end + if (s_wvalid && !w_have && !s_wready) begin s_wready <= 1'b1; w_have <= 1'b1; w_val <= s_wdata; end + if (s_arvalid && !ar_have && !s_arready && host_ph == 3'd0) begin s_arready <= 1'b1; ar_have <= 1'b1; ar_word <= s_araddr[17:2]; end + if (s_bvalid && s_bready) s_bvalid <= 1'b0; + if (s_rvalid && s_rready) s_rvalid <= 1'b0; + + // ---- the DMA: requests, beats, entries ---- + if (dma_busy && !m_arvalid && dma_reqs != 32'd0) begin + burst_len <= (dma_reqs > 32'd16) ? 8'd15 : dma_reqs[7:0] - 8'd1; + m_arvalid <= 1'b1; + end else if (m_arvalid && m_arready) begin + m_arvalid <= 1'b0; + m_araddr <= m_araddr + ({41'd0, burst_len} + 49'd1) * 49'd8; + dma_reqs <= dma_reqs - ({24'd0, burst_len} + 32'd1); + end + if (m_rvalid && m_rready) begin + dma_left <= dma_left - 32'd1; + if (m_rresp != 2'b00) dma_err <= 1'b1; + else if (!dma_err) begin ent <= m_rdata; ent_have <= 1'b1; end + end + if (dma_busy && dma_left == 32'd0 && dma_reqs == 32'd0 && !m_arvalid && !ent_have) begin + dma_busy <= 1'b0; + dma_done_count <= dma_done_count + 32'd1; + end + + if (ent_have) begin + ent_have <= 1'b0; + if (ent[47:32] < 16'h8000) begin + stb <= 1'b1; we <= 1'b1; addr <= ent[47:32]; wdata <= ent[31:0]; + end else if (ent[47:32] >= 16'h9000 && ent[47:32] < 16'h9800) begin + cr_we <= 1'b1; cr_wa <= ent[42:32]; cr_wd <= ent[31:0]; + end + end else if (seq_busy) begin + // per chain: six writes, a start, a wait, six reads into the result RAM + case (seq_ph) + 4'd0: seq_ph <= 4'd1; // chain_q follows {seq_k, seq_i} + 4'd1: begin + stb <= 1'b1; we <= 1'b1; wdata <= chain_q; + addr <= (seq_i < 3'd4) ? (16'h0100 + {13'd0, seq_i}) : (16'h0010 + {13'd0, seq_i - 3'd4}); + if (seq_i == 3'd5) begin seq_i <= 3'd0; seq_ph <= 4'd2; end + else begin seq_i <= seq_i + 3'd1; seq_ph <= 4'd0; end + end + 4'd2: begin stb <= 1'b1; we <= 1'b1; addr <= 16'h0003; wdata <= 32'd1; seq_wait <= 4'd3; seq_ph <= 4'd3; end + 4'd3: begin + if (seq_wait != 4'd0) seq_wait <= seq_wait - 4'd1; + else if (!busy) begin seq_i <= 3'd0; seq_ph <= 4'd4; end + end + 4'd4: begin + stb <= 1'b1; we <= 1'b0; + addr <= (seq_i < 3'd4) ? (16'h0110 + {13'd0, seq_i}) : ((seq_i == 3'd4) ? 16'h0028 : 16'h0002); + seq_ph <= 4'd5; + end + 4'd5: seq_ph <= 4'd6; // the engine registers its read data on the clock after stb + 4'd6: begin + rr_we <= 1'b1; rr_wa <= {seq_k[7:0], seq_i}; rr_wd <= rdata; + if (seq_i == 3'd5) begin + seq_i <= 3'd0; + seq_done_count <= seq_done_count + 16'd1; + if (seq_k + 16'd1 == {7'd0, seq_count}) seq_busy <= 1'b0; + seq_k <= seq_k + 16'd1; + seq_ph <= 4'd0; + end else begin seq_i <= seq_i + 3'd1; seq_ph <= 4'd4; end + end + default: seq_ph <= 4'd0; + endcase + end + + // ---- the host ---- + if (aw_have && w_have && host_ph == 3'd0 && !s_bvalid && !ent_have) begin + if (aw_word < 16'h8000) begin + if (bus_free) begin stb <= 1'b1; we <= 1'b1; addr <= aw_word; wdata <= w_val; end + else drops <= drops + 32'd1; + end else begin + case (aw_word) + 16'h8002: dma_lo <= w_val; + 16'h8003: dma_hi <= w_val; + 16'h8004: dma_count <= w_val; + 16'h8005: if (bus_free && dma_count != 32'd0) begin + dma_busy <= 1'b1; dma_err <= 1'b0; dma_left <= dma_count; dma_reqs <= dma_count; + m_araddr <= {dma_hi[16:0], dma_lo}; + end + 16'h8008: drops <= 32'd0; + 16'h8010: seq_count <= (w_val > 32'd256) ? 9'd256 : w_val[8:0]; + 16'h8011: if (bus_free && seq_count != 9'd0) begin + seq_busy <= 1'b1; seq_k <= 16'd0; seq_ph <= 4'd0; seq_i <= 3'd0; + end + 16'h8013: if (seq_busy) begin + // abandon the batch and stop the engine's job + seq_busy <= 1'b0; + stb <= 1'b1; we <= 1'b1; addr <= 16'h0003; wdata <= 32'd4; + end + default: if (aw_word >= 16'h9000 && aw_word < 16'h9800) begin + cr_we <= 1'b1; cr_wa <= aw_word[10:0]; cr_wd <= w_val; + end + endcase + end + aw_have <= 1'b0; w_have <= 1'b0; s_bvalid <= 1'b1; + end else if (ar_have && host_ph == 3'd0 && !s_rvalid) begin + if (ar_word < 16'h8000) begin + if (bus_free) begin stb <= 1'b1; we <= 1'b0; addr <= ar_word; host_ph <= 3'd2; end + else begin drops <= drops + 32'd1; wrap_rdata <= 32'hDEAD_BEEF; host_ph <= 3'd4; end + end else begin + case (ar_word) + 16'h8000: wrap_rdata <= LAXI; + 16'h8001: wrap_rdata <= {29'd0, dma_err, dma_busy, seq_busy}; + 16'h8002: wrap_rdata <= dma_lo; + 16'h8003: wrap_rdata <= dma_hi; + 16'h8004: wrap_rdata <= dma_count; + 16'h8006: wrap_rdata <= dma_done_count; + 16'h8007: wrap_rdata <= dma_left; + 16'h8008: wrap_rdata <= drops; + 16'h8009: wrap_rdata <= WRAP_VERSION; + 16'h8010: wrap_rdata <= {23'd0, seq_count}; + 16'h8012: wrap_rdata <= {16'd0, seq_done_count}; + default: wrap_rdata <= 32'hDEAD_BEEF; + endcase + host_ph <= (ar_word >= 16'hA000 && ar_word < 16'hA800) ? 3'd5 : 3'd4; + end + end + if (host_ph == 3'd2) host_ph <= 3'd3; + else if (host_ph == 3'd3) begin s_rdata <= rdata; s_rvalid <= 1'b1; ar_have <= 1'b0; host_ph <= 3'd0; end + else if (host_ph == 3'd4) begin s_rdata <= wrap_rdata; s_rvalid <= 1'b1; ar_have <= 1'b0; host_ph <= 3'd0; end + else if (host_ph == 3'd5) begin s_rdata <= res_q; s_rvalid <= 1'b1; ar_have <= 1'b0; host_ph <= 3'd0; end + end + end +endmodule + +`default_nettype wire diff --git a/wai/wai-quantum-landscape-fpga/rtl/landscape_top_axi.v b/wai/wai-quantum-landscape-fpga/rtl/landscape_top_axi.v new file mode 100644 index 000000000..ffb577ba5 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/rtl/landscape_top_axi.v @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// KV260 top for the AXI link: the processing system gives the clock, an AXI-Lite master (its +// low-power-domain port, through a protocol converter), a high-performance slave port into DDR, +// and the EMIO lines. The reset is the fabric's own: a counter that holds the link's control in +// reset for 128 clocks after configuration, so the design does not depend on the state the +// processor's PL reset line was left in. +`timescale 1ns/1ps +module design_top(); + wire pl_clk0; + reg [7:0] por = 8'd0; + always @(posedge pl_clk0) if (!por[7]) por <= por + 8'd1; + wire rstn = por[7]; + wire [39:0] l_awaddr, l_araddr; + wire [31:0] l_wdata, l_rdata; + wire l_awvalid, l_awready, l_wvalid, l_wready, l_bvalid, l_bready, l_arvalid, l_arready, l_rvalid, l_rready; + wire [1:0] l_bresp, l_rresp; + wire [3:0] l_wstrb; + wire [2:0] l_awprot, l_arprot; + wire [48:0] d_araddr; + wire [7:0] d_arlen; + wire [2:0] d_arsize, d_arprot; + wire [1:0] d_arburst, d_rresp; + wire [3:0] d_arcache; + wire d_arvalid, d_arready, d_rlast, d_rvalid, d_rready; + wire [5:0] d_rid, d_bid; + wire [1:0] d_bresp; + wire d_awready, d_wready, d_bvalid; + wire [63:0] d_rdata; + wire [94:0] emio_o, emio_i; + sys_wrapper ps_i ( + .pl_clk0(pl_clk0), .aresetn(rstn), .emio_o(emio_o), .emio_i(emio_i), + .LITE_awaddr(l_awaddr), .LITE_awprot(l_awprot), .LITE_awvalid(l_awvalid), .LITE_awready(l_awready), + .LITE_wdata(l_wdata), .LITE_wstrb(l_wstrb), .LITE_wvalid(l_wvalid), .LITE_wready(l_wready), + .LITE_bresp(l_bresp), .LITE_bvalid(l_bvalid), .LITE_bready(l_bready), + .LITE_araddr(l_araddr), .LITE_arprot(l_arprot), .LITE_arvalid(l_arvalid), .LITE_arready(l_arready), + .LITE_rdata(l_rdata), .LITE_rresp(l_rresp), .LITE_rvalid(l_rvalid), .LITE_rready(l_rready), + .DMA_araddr(d_araddr), .DMA_arlen(d_arlen), .DMA_arsize(d_arsize), .DMA_arburst(d_arburst), + .DMA_arcache(d_arcache), .DMA_arprot(d_arprot), .DMA_arvalid(d_arvalid), .DMA_arready(d_arready), + .DMA_arlock(1'b0), .DMA_arqos(4'd0), .DMA_arid(6'd0), .DMA_aruser(1'b0), + .DMA_rdata(d_rdata), .DMA_rresp(d_rresp), .DMA_rlast(d_rlast), .DMA_rvalid(d_rvalid), .DMA_rready(d_rready), + .DMA_rid(d_rid), + .DMA_awaddr(49'd0), .DMA_awlen(8'd0), .DMA_awsize(3'd3), .DMA_awburst(2'b01), .DMA_awlock(1'b0), + .DMA_awcache(4'd0), .DMA_awprot(3'd0), .DMA_awqos(4'd0), .DMA_awvalid(1'b0), .DMA_awid(6'd0), .DMA_awuser(1'b0), + .DMA_awready(d_awready), .DMA_wdata(64'd0), .DMA_wstrb(8'd0), .DMA_wlast(1'b0), .DMA_wvalid(1'b0), .DMA_wready(d_wready), + .DMA_bid(d_bid), .DMA_bresp(d_bresp), .DMA_bvalid(d_bvalid), .DMA_bready(1'b1) + ); + landscape_axi u ( + .clk(pl_clk0), .rstn(rstn), + .s_awaddr(l_awaddr[31:0]), .s_awvalid(l_awvalid), .s_awready(l_awready), .s_wdata(l_wdata), .s_wvalid(l_wvalid), + .s_wready(l_wready), .s_bresp(l_bresp), .s_bvalid(l_bvalid), .s_bready(l_bready), + .s_araddr(l_araddr[31:0]), .s_arvalid(l_arvalid), .s_arready(l_arready), .s_rdata(l_rdata), .s_rresp(l_rresp), + .s_rvalid(l_rvalid), .s_rready(l_rready), + .m_araddr(d_araddr), .m_arlen(d_arlen), .m_arsize(d_arsize), .m_arburst(d_arburst), .m_arcache(d_arcache), + .m_arprot(d_arprot), .m_arvalid(d_arvalid), .m_arready(d_arready), .m_rdata(d_rdata), .m_rresp(d_rresp), + .m_rlast(d_rlast), .m_rvalid(d_rvalid), .m_rready(d_rready), + .emio_i(emio_i) + ); +endmodule diff --git a/wai/wai-quantum-landscape-fpga/scripts/build_bitstream.sh b/wai/wai-quantum-landscape-fpga/scripts/build_bitstream.sh index 3cfca50df..9379378c6 100755 --- a/wai/wai-quantum-landscape-fpga/scripts/build_bitstream.sh +++ b/wai/wai-quantum-landscape-fpga/scripts/build_bitstream.sh @@ -4,13 +4,16 @@ # PATH, at the version this design was built and measured with: Vivado 2026.1. Its no-charge licence # tier covers this part; the design uses no licensed IP (the processing system's configuration block # only). Run from an empty directory: -# sh /path/to/wai-quantum-landscape-fpga/scripts/build_bitstream.sh [MHz] [cores] +# sh /path/to/wai-quantum-landscape-fpga/scripts/build_bitstream.sh [MHz] [cores] [axi|emio] +# The AXI link is the default; the multi-core build (cores above 1) has the EMIO link only. set -e FREQ=${1:-100} CORES=${2:-1} +LINK=${3:-axi} +if [ "$CORES" -gt 1 ]; then LINK=emio; fi HERE=$(cd "$(dirname "$0")/.." && pwd) vivado -version | head -1 -vivado -mode batch -nojournal -source "$HERE/landscape.tcl" -tclargs FREQ "$FREQ" TAG landscape JOBS 4 CORES "$CORES" > build.log 2>&1 +vivado -mode batch -nojournal -source "$HERE/landscape.tcl" -tclargs FREQ "$FREQ" TAG landscape JOBS 4 CORES "$CORES" LINK "$LINK" > build.log 2>&1 grep -E "IMPL_STATUS|BITSTREAM" build.log BIT=$(ls ev_landscape/ev_landscape.runs/impl_1/*.bit) printf 'all:\n{\n %s\n}\n' "$BIT" > landscape.bif diff --git a/wai/wai-quantum-landscape-fpga/scripts/run_all.sh b/wai/wai-quantum-landscape-fpga/scripts/run_all.sh index e805de60a..fc570e81c 100755 --- a/wai/wai-quantum-landscape-fpga/scripts/run_all.sh +++ b/wai/wai-quantum-landscape-fpga/scripts/run_all.sh @@ -1,9 +1,9 @@ #!/bin/sh # SPDX-License-Identifier: Apache-2.0 # Everything a builder runs on their own board, in order: load the engine, the conformance set, the -# worked examples against the CPU reference, throughput, then the power campaign. On the board, as -# root, from a directory holding wai-quantum-landscape-fpga, landscape.bit.bin, this script and -# power_campaign.sh: +# worked examples against the CPU reference, the joint statistics against exact enumeration, the +# processor-to-logic latency, throughput, then the power campaign. On the board, as root, from a +# directory holding wai-quantum-landscape-fpga, landscape.bit.bin, this script and power_campaign.sh: # sudo setsid nohup sh run_all.sh > /dev/null 2>&1 & # and leave the board alone until results-*/power/DONE appears (about three hours). It refuses to # start, and stops between steps, if another session is flashing or measuring. @@ -23,6 +23,8 @@ echo 0 > /sys/class/fpga_manager/fpga0/flags ./wai-quantum-landscape-fpga status > "$OUT/status.txt" 2>&1 ./wai-quantum-landscape-fpga conform > "$OUT/conform.txt" 2>&1 ./wai-quantum-landscape-fpga agree --seeds 200 > "$OUT/agree.txt" 2>&1 +./wai-quantum-landscape-fpga joint > "$OUT/joint.txt" 2>&1 +./wai-quantum-landscape-fpga latency > "$OUT/latency.txt" 2>&1 ./wai-quantum-landscape-fpga throughput > "$OUT/throughput.txt" 2>&1 sh power_campaign.sh "$OUT" ./wai-quantum-landscape-fpga release >> "$OUT/status.txt" 2>&1 diff --git a/wai/wai-quantum-landscape-fpga/src/axi.rs b/wai/wai-quantum-landscape-fpga/src/axi.rs new file mode 100644 index 000000000..afd4631f0 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/src/axi.rs @@ -0,0 +1,389 @@ +//! The board's AXI link (`rtl/landscape_axi.v`): the engine's registers on an AXI-Lite port at +//! 0x8000_0000, a write-list DMA that reads bulk loads from DDR, and a sequencer that runs a batch +//! of chains in fabric. +//! +//! An AXI access to a programmable-logic address that nothing answers never completes, and it holds +//! the processor until the board is power-cycled. This board is shared and remote, and another +//! session may replace the design. So every access is preceded by a read of the link's signature on +//! EMIO bank 3 (a processor register, which always answers): if the signature is not there, the +//! access is not made. The link itself answers every access within a few clocks whatever the +//! engine is doing (an engine-map access while the DMA or the sequencer holds the engine is +//! dropped and counted, never queued), and this port never makes one: it checks DROPS after every +//! batch. + +/// The wrapper's registers and RAMs, as word addresses on the link. +pub const WRAP_ID: u16 = 0x8000; +pub const WRAP_ID_VALUE: u32 = 0x4C41_5849; // "LAXI" +pub const WRAP_STATUS: u16 = 0x8001; // [0] sequencer busy [1] DMA busy [2] DMA error +pub const DMA_LO: u16 = 0x8002; +pub const DMA_HI: u16 = 0x8003; +pub const DMA_COUNT: u16 = 0x8004; +pub const DMA_GO: u16 = 0x8005; +pub const DMA_DONE: u16 = 0x8006; +pub const DMA_LEFT: u16 = 0x8007; +pub const WRAP_DROPS: u16 = 0x8008; +pub const WRAP_VERSION: u16 = 0x8009; +pub const WRAP_VERSION_VALUE: u32 = 1; +pub const SEQ_COUNT: u16 = 0x8010; +pub const SEQ_GO: u16 = 0x8011; +pub const SEQ_DONE: u16 = 0x8012; +pub const SEQ_ABORT: u16 = 0x8013; +pub const CHAIN_RAM: u16 = 0x9000; +pub const RESULT_RAM: u16 = 0xA000; +/// Chains the chain RAM holds. +pub const SEQ_MAX: usize = 256; +/// The EMIO signature: bank 3, bits 7:0. +pub const SIGNATURE: u32 = 0xA5; +/// The link's physical address and size. +pub const LITE_BASE: u64 = 0x8000_0000; +pub const LITE_LEN: usize = 0x4_0000; + +pub const ST_SEQ: u32 = 1; +pub const ST_DMA: u32 = 2; +pub const ST_DMA_ERR: u32 = 4; + +#[cfg(unix)] +pub use board_port::*; + +#[cfg(unix)] +mod board_port { + use super::*; + use crate::emio::Mem; + use crate::port::{ChainWords, Error, Port, ResultWords, batch_plain, bulk_plain, err}; + use crate::regs::STATUS; + use std::fs::File; + use std::os::unix::fs::FileExt; + use std::time::{Duration, Instant}; + + const GPIO_BASE: u64 = 0xFF0A_0000; + const DATA_3_RO: usize = 0x06C; + + unsafe extern "C" { + fn mmap(addr: *mut u8, len: usize, prot: i32, flags: i32, fd: i32, off: i64) -> *mut u8; + fn munmap(addr: *mut u8, len: usize) -> i32; + fn sysconf(name: i32) -> i64; + } + const PROT_RW: i32 = 1 | 2; + const MAP_PRIVATE: i32 = 0x02; + const MAP_ANONYMOUS: i32 = 0x20; + const MAP_LOCKED: i32 = 0x2000; + const MAP_POPULATE: i32 = 0x8000; + const SC_PAGESIZE: i32 = 30; + + /// The data cache's line size, from CTR_EL0. + #[cfg(target_arch = "aarch64")] + fn dline() -> usize { + let ctr: u64; + unsafe { core::arch::asm!("mrs {0}, ctr_el0", out(reg) ctr) }; + 4 << ((ctr >> 16) & 0xF) + } + + /// Cleans `len` bytes from `p` to the point of coherency: the DMA port is not coherent with the + /// processor's caches, so a list the processor wrote must be in DDR before the link reads it. + #[cfg(target_arch = "aarch64")] + fn clean(p: *const u8, len: usize) { + let line = dline(); + let mut a = p as usize & !(line - 1); + let end = p as usize + len; + while a < end { + unsafe { core::arch::asm!("dc cvac, {0}", in(reg) a) }; + a += line; + } + unsafe { core::arch::asm!("dsb sy") }; + } + #[cfg(not(target_arch = "aarch64"))] + fn clean(_p: *const u8, _len: usize) {} + + /// Pinned pages for write lists, and their physical addresses from /proc/self/pagemap (root). + pub struct Dma { + buf: *mut u8, + pages: usize, + page: usize, + pagemap: File, + } + + impl Dma { + pub fn new(pages: usize) -> Result { + let page = unsafe { sysconf(SC_PAGESIZE) } as usize; + if !(4096..=65536).contains(&page) { + return err(format!("page size {page}")); + } + let len = pages * page; + let buf = unsafe { mmap(std::ptr::null_mut(), len, PROT_RW, MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED | MAP_POPULATE, -1, 0) }; + if buf as isize == -1 || buf.is_null() { + return err("could not pin pages for the DMA's lists"); + } + let pagemap = File::open("/proc/self/pagemap").map_err(|e| Error(format!("/proc/self/pagemap: {e}")))?; + let d = Dma { buf, pages, page, pagemap }; + for k in 0..pages { + d.phys(k)?; + } + Ok(d) + } + + /// Entries a page holds. + pub fn per_page(&self) -> usize { + self.page / 8 + } + + /// The physical address of page `k`, read afresh each time it is used. + pub fn phys(&self, k: usize) -> Result { + let va = self.buf as usize + k * self.page; + let mut e = [0u8; 8]; + self.pagemap.read_exact_at(&mut e, (va / self.page * 8) as u64).map_err(|x| Error(format!("pagemap: {x}")))?; + let e = u64::from_le_bytes(e); + let pfn = e & ((1 << 55) - 1); + if e >> 63 == 0 || pfn == 0 { + return err("no physical address for a DMA page (run as root)"); + } + Ok(pfn * self.page as u64) + } + + fn fill(&self, k: usize, list: &[(u16, u32)]) { + let p = unsafe { self.buf.add(k * self.page) } as *mut u64; + for (i, &(a, v)) in list.iter().enumerate() { + unsafe { std::ptr::write_volatile(p.add(i), ((a as u64) << 32) | v as u64) }; + } + clean(p as *const u8, list.len() * 8); + } + } + + impl Drop for Dma { + fn drop(&mut self) { + unsafe { + munmap(self.buf, self.pages * self.page); + } + } + } + + /// Whether the loaded design carries the AXI link's signature: a GPIO read, safe whatever is loaded. + pub fn signature_present() -> bool { + Mem::map(GPIO_BASE, 0x1000).map(|g| g.rd(DATA_3_RO) & 0xFF == SIGNATURE).unwrap_or(false) + } + + pub struct AxiPort { + lite: Mem, + gpio: Mem, + /// Check the signature before every access (on unless a measurement turns it off). + pub guard: bool, + pub dma: Option, + /// Lists shorter than this go as plain writes. + pub dma_min: usize, + /// Batches go to the sequencer; off, they go as plain accesses (for comparison). + pub sequencer: bool, + pub accesses: u64, + pub dma_lists: u64, + pub batches: u64, + } + + impl AxiPort { + /// Opens the link: the signature first, then the wrapper's ID and version over AXI. + pub fn open() -> Result { + let gpio = Mem::map(GPIO_BASE, 0x1000)?; + let sig = gpio.rd(DATA_3_RO); + if sig & 0xFF != SIGNATURE { + return err(format!( + "the programmable logic does not carry the AXI link's signature (EMIO bank 3 reads {sig:#010x}, want {SIGNATURE:#x} in its low byte), \ + so no AXI address is touched. Load the AXI build first; if it was loaded, another session has replaced it." + )); + } + let lite = Mem::map(LITE_BASE, LITE_LEN)?; + let mut p = AxiPort { lite, gpio, guard: true, dma: None, dma_min: 32, sequencer: true, accesses: 0, dma_lists: 0, batches: 0 }; + let id = p.rd(WRAP_ID)?; + if id != WRAP_ID_VALUE { + return err(format!("the AXI link answers {id:#010x} for its ID, want {WRAP_ID_VALUE:#010x}")); + } + let v = p.rd(WRAP_VERSION)?; + if v != WRAP_VERSION_VALUE { + return err(format!("AXI link version {v}, this host speaks {WRAP_VERSION_VALUE}")); + } + let st = p.rd(WRAP_STATUS)?; + if st & (ST_SEQ | ST_DMA) != 0 { + return err(format!("the AXI link is busy at open (status {st:#x}): another process is using it")); + } + p.wr(WRAP_DROPS, 0)?; + p.dma = Some(Dma::new(16)?); + Ok(p) + } + + pub fn name(&self) -> &'static str { + "AXI" + } + + #[inline] + pub fn signature(&self) -> u32 { + self.gpio.rd(DATA_3_RO) + } + + #[inline] + fn check(&self) -> Result<(), Error> { + if self.guard && self.gpio.rd(DATA_3_RO) & 0xFF != SIGNATURE { + return err("the AXI link's signature is gone: another session has replaced the design. Stopped before the next AXI access."); + } + Ok(()) + } + + /// A read of any word on the link, guarded. + #[inline] + pub fn rd(&mut self, word: u16) -> Result { + self.check()?; + self.accesses += 1; + Ok(self.lite.rd(word as usize * 4)) + } + + /// A write of any word on the link, guarded. + #[inline] + pub fn wr(&mut self, word: u16, v: u32) -> Result<(), Error> { + self.check()?; + self.accesses += 1; + self.lite.wr(word as usize * 4, v); + Ok(()) + } + + /// Unguarded, for the latency measurement only. + #[inline] + pub fn rd_raw(&self, word: u16) -> u32 { + self.lite.rd(word as usize * 4) + } + #[inline] + pub fn wr_raw(&self, word: u16, v: u32) { + self.lite.wr(word as usize * 4, v) + } + + fn wait_wrap(&mut self, bits: u32, timeout: Duration, what: &str) -> Result { + let t0 = Instant::now(); + let mut k = 0u32; + loop { + let st = self.rd(WRAP_STATUS)?; + if st & bits == 0 { + return Ok(st); + } + k += 1; + if k % 256 == 0 && t0.elapsed() > timeout { + return err(format!("{what} still busy after {timeout:?} (status {st:#x})")); + } + } + } + + /// One list of at most a page, by DMA, waited for. + fn dma_page(&mut self, slot: usize, list: &[(u16, u32)], timeout: Duration) -> Result<(), Error> { + let pa = { + let d = self.dma.as_ref().ok_or_else(|| Error("no DMA pages".into()))?; + d.fill(slot, list); + d.phys(slot)? + }; + let before = self.rd(DMA_DONE)?; + self.wr(DMA_LO, pa as u32)?; + self.wr(DMA_HI, (pa >> 32) as u32)?; + self.wr(DMA_COUNT, list.len() as u32)?; + self.wr(DMA_GO, 1)?; + let st = self.wait_wrap(ST_DMA, timeout, "the DMA")?; + if st & ST_DMA_ERR != 0 { + return err(format!("the DMA's read of the list at {pa:#x} came back with an error")); + } + let after = self.rd(DMA_DONE)?; + if after != before.wrapping_add(1) { + return err(format!("the DMA did not run (done count {before} -> {after})")); + } + self.dma_lists += 1; + Ok(()) + } + + /// A write list of any length, a page at a time. + pub fn dma_list(&mut self, list: &[(u16, u32)], timeout: Duration) -> Result<(), Error> { + let (per, pages) = match &self.dma { + Some(d) => (d.per_page(), d.pages), + None => return bulk_plain(self, list), + }; + for (k, chunk) in list.chunks(per).enumerate() { + self.dma_page(k % pages, chunk, timeout)?; + } + Ok(()) + } + + fn drops_zero(&mut self) -> Result<(), Error> { + let d = self.rd(WRAP_DROPS)?; + if d != 0 { + return err(format!("{d} engine accesses were dropped while the link held the engine")); + } + Ok(()) + } + } + + impl Port for AxiPort { + fn write(&mut self, addr: u16, val: u32) -> Result<(), Error> { + if addr >= 0x8000 { + return err(format!("{addr:#06x} is not an engine register")); + } + self.wr(addr, val) + } + fn read(&mut self, addr: u16) -> Result { + if addr >= 0x8000 { + return err(format!("{addr:#06x} is not an engine register")); + } + self.rd(addr) + } + fn wait_idle(&mut self, timeout: Duration) -> Result<(), Error> { + let t0 = Instant::now(); + let mut k = 0u32; + loop { + let st = self.rd(STATUS)?; + if st & 1 == 0 { + return Ok(()); + } + k += 1; + if k % 256 == 0 && t0.elapsed() > timeout { + return err(format!("the engine still busy after {timeout:?} (status {st:#x})")); + } + } + } + fn bulk(&mut self, list: &[(u16, u32)]) -> Result<(), Error> { + if list.len() < self.dma_min || self.dma.is_none() { + return bulk_plain(self, list); + } + self.dma_list(list, Duration::from_secs(5)) + } + fn batch(&mut self, chains: &[ChainWords], timeout: Duration) -> Result, Error> { + if !self.sequencer { + return batch_plain(self, chains, timeout); + } + let mut out = Vec::with_capacity(chains.len()); + for group in chains.chunks(SEQ_MAX) { + let mut list = Vec::with_capacity(group.len() * 6); + for (k, c) in group.iter().enumerate() { + for (i, &w) in c.iter().enumerate() { + list.push((CHAIN_RAM + (8 * k + i) as u16, w)); + } + } + if self.dma.is_some() { + self.dma_list(&list, timeout)?; + } else { + for &(a, v) in &list { + self.wr(a, v)?; + } + } + let before = self.rd(SEQ_DONE)?; + self.wr(SEQ_COUNT, group.len() as u32)?; + self.wr(SEQ_GO, 1)?; + if let Err(e) = self.wait_wrap(ST_SEQ, timeout, "the sequencer") { + let _ = self.wr(SEQ_ABORT, 1); + return Err(e); + } + let after = self.rd(SEQ_DONE)?; + if after.wrapping_sub(before) & 0xFFFF != group.len() as u32 { + return err(format!("the sequencer ran {} chains of {}", after.wrapping_sub(before) & 0xFFFF, group.len())); + } + for k in 0..group.len() { + let mut r = [0u32; 6]; + for (i, w) in r.iter_mut().enumerate() { + *w = self.rd(RESULT_RAM + (8 * k + i) as u16)?; + } + out.push(r); + } + self.drops_zero()?; + self.batches += 1; + } + Ok(out) + } + } +} diff --git a/wai/wai-quantum-landscape-fpga/src/board.rs b/wai/wai-quantum-landscape-fpga/src/board.rs index 3690da79f..2458d6b62 100644 --- a/wai/wai-quantum-landscape-fpga/src/board.rs +++ b/wai/wai-quantum-landscape-fpga/src/board.rs @@ -19,6 +19,20 @@ pub fn dmesg_fpga_writes() -> Result, Error> { .collect()) } +/// This process and every process it descends from: a campaign script's own command line may match +/// the patterns below (its arguments name the vehicles it runs), and it is not another session. +fn ancestors() -> Vec { + let mut out = Vec::new(); + let mut pid = std::process::id(); + while pid > 1 && out.len() < 64 { + out.push(pid); + let stat = fs::read_to_string(format!("/proc/{pid}/stat")).unwrap_or_default(); + // the fields after the command's closing parenthesis: state, then the parent's id + pid = stat.rsplit_once(')').and_then(|(_, rest)| rest.split_whitespace().nth(1)).and_then(|v| v.parse().ok()).unwrap_or(0); + } + out +} + /// Processes that, by the board's written rules, mean another session is flashing or measuring. pub fn foreign_sessions() -> String { // Override the patterns for another board's habits: WAI_QUANTUM_LANDSCAPE_FPGA_FOREIGN, an extended regex. @@ -27,7 +41,15 @@ pub fn foreign_sessions() -> String { .arg("-c") .arg(format!("pgrep -af '{pat}' | grep -v -e pgrep -e wai-quantum-landscape-fpga || true")) .output(); - out.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()).unwrap_or_default() + let mine = ancestors(); + out.map(|o| { + String::from_utf8_lossy(&o.stdout) + .lines() + .filter(|l| l.split_whitespace().next().and_then(|p| p.parse::().ok()).is_none_or(|p| !mine.contains(&p))) + .collect::>() + .join("\n") + }) + .unwrap_or_default() } /// Loads a bitstream through the FPGA manager and proves the load by its dmesg line. diff --git a/wai/wai-quantum-landscape-fpga/src/emio.rs b/wai/wai-quantum-landscape-fpga/src/emio.rs index 4773fc316..c4d93e040 100644 --- a/wai/wai-quantum-landscape-fpga/src/emio.rs +++ b/wai/wai-quantum-landscape-fpga/src/emio.rs @@ -76,6 +76,10 @@ pub struct EmioPort { } impl EmioPort { + pub fn name(&self) -> &'static str { + "EMIO" + } + pub fn open() -> Result { let m = Mem::map(GPIO_BASE, 0x1000)?; let saved = [m.rd(DIRM_3), m.rd(OEN_3), m.rd(DIRM_4), m.rd(OEN_4), m.rd(DIRM_5), m.rd(OEN_5), m.rd(DATA_5)]; diff --git a/wai/wai-quantum-landscape-fpga/src/engine.rs b/wai/wai-quantum-landscape-fpga/src/engine.rs index 58920d7b5..ffa2272fa 100644 --- a/wai/wai-quantum-landscape-fpga/src/engine.rs +++ b/wai/wai-quantum-landscape-fpga/src/engine.rs @@ -80,6 +80,14 @@ impl Loaded { } } +/// One chain of a batch: where it ended, after how many sweeps (or steps, at temperature 0), and how. +#[derive(Clone, Debug, PartialEq)] +pub struct BatchRun { + pub state: Vec, + pub steps: u32, + pub term: u32, +} + pub struct Engine { pub port: P, pub timeout: Duration, @@ -133,7 +141,7 @@ impl Engine

{ return Ok(()); } self.loaded = None; - self.port.write(CONTROL, CTL_CLEARW)?; + let mut list = vec![(CONTROL, CTL_CLEARW)]; for (i, row) in w.rows.iter().enumerate() { let mut cells = vec![0i8; CELLS]; for &(j, v) in row { @@ -144,15 +152,16 @@ impl Engine

{ } for (k, &word) in encode(&cells).iter().enumerate() { if word != 0 { - self.port.write(W + (i as u16) * 4 + k as u16, word)?; + list.push((W + (i as u16) * 4 + k as u16, word)); } } if w.fields[i] != 0 { - self.port.write(FIELDS + i as u16, (w.fields[i] as u32) & 0xF_FFFF)?; + list.push((FIELDS + i as u16, (w.fields[i] as u32) & 0xF_FFFF)); } } - self.port.write(N_M1, (w.n - 1) as u32)?; - self.port.write(COST, w.cost as u32)?; + list.push((N_M1, (w.n - 1) as u32)); + list.push((COST, w.cost as u32)); + self.port.bulk(&list)?; self.n = w.n; self.loaded = Some(key); Ok(()) @@ -176,11 +185,9 @@ impl Engine

{ } } let key = *h.finalize().as_bytes(); - self.port.write(WIN_LO, (win_lo as u32) & 0x3F_FFFF)?; - self.port.write(WBITS, wbits)?; - self.port.write(LEVELS_M1, (levels.len() - 1) as u32)?; + let mut list = vec![(WIN_LO, (win_lo as u32) & 0x3F_FFFF), (WBITS, wbits), (LEVELS_M1, (levels.len() - 1) as u32)]; if self.table == Some(key) { - return Ok(()); + return self.port.bulk(&list); } self.table = None; for (l, entries) in levels.iter().enumerate() { @@ -191,14 +198,12 @@ impl Engine

{ if off < 0 || off as usize + entries.len() > (1usize << wbits) { return err("table entries outside the window"); } - self.port.write(TBL_ADDR, ((l << wbits) + off as usize) as u32)?; + list.push((TBL_ADDR, ((l << wbits) + off as usize) as u32)); for &(a, b) in entries { - self.port.write(TBL_A_LO, a as u32)?; - self.port.write(TBL_A_HI, (a >> 32) as u32)?; - self.port.write(TBL_B_LO, b as u32)?; - self.port.write(TBL_B_HI, (b >> 32) as u32)?; + list.extend([(TBL_A_LO, a as u32), (TBL_A_HI, (a >> 32) as u32), (TBL_B_LO, b as u32), (TBL_B_HI, (b >> 32) as u32)]); } } + self.port.bulk(&list)?; self.table = Some(key); Ok(()) } @@ -327,6 +332,29 @@ impl Engine

{ self.port.write(CONTROL, CTL_START) } + /// A run of chains on the job already set (mode, bound, sweeps per level, table, workload): + /// for each, its start and the draw counter's start, `x0`. On the AXI link the chains run in + /// fabric, one after another, with nothing from the processor between them. + pub fn run_batch(&mut self, mode: u32, bound: u32, spl: u32, final_t0: bool, chains: &[(Vec, u64)]) -> Result, Error> { + self.port.bulk(&[(MODE, mode | if final_t0 { MODE_FINAL_T0 } else { 0 }), (BOUND, bound), (SPL, spl.max(1))])?; + let mut words = Vec::with_capacity(chains.len()); + for (start, x0) in chains { + if start.len() != self.n { + return err(format!("a start of {} cells for {} variables", start.len(), self.n)); + } + let b = encode(start); + words.push([b[0], b[1], b[2], b[3], *x0 as u32, (*x0 >> 32) as u32]); + } + let mut out = Vec::with_capacity(chains.len()); + for r in self.port.batch(&words, self.timeout)? { + if r[5] & 2 == 0 { + return err(format!("a chain of the batch stopped without finishing (status {:#x})", r[5])); + } + out.push(BatchRun { state: decode(&[r[0], r[1], r[2], r[3]], self.n)?, steps: r[4], term: (r[5] >> 2) & 3 }); + } + Ok(out) + } + pub fn halt(&mut self) -> Result<(), Error> { self.port.write(CONTROL, CTL_STOP)?; let m = self.port.read(MODE)?; diff --git a/wai/wai-quantum-landscape-fpga/src/exact.rs b/wai/wai-quantum-landscape-fpga/src/exact.rs new file mode 100644 index 000000000..be0b9f26c --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/src/exact.rs @@ -0,0 +1,464 @@ +//! Joint statistics against exact enumeration. +//! +//! Per-cell frequencies can all be right while the joint law is wrong: a sampler that drew each cell +//! from its correct marginal, independently, would pass every per-cell test. So the sample kernel is +//! also held to the exact law of the whole state. For a workload of `n` cells (3^n states, `n` up to +//! about 10) the law of a chain's final state is computed by enumeration: a vector over every state, +//! pushed through each cell update of each sweep with the update's three probabilities taken from +//! the reference's own cut points (a draw `u` of 53 bits gives −1 below `a`, 0 below `b`, +1 above, +//! so the probabilities are `a/2^53`, `(b−a)/2^53` and `1 − b/2^53`), then through the synchronous +//! sweep at temperature 0 where the schedule ends with one. Nothing is sampled and nothing +//! approximated beyond the f64 sums. +//! +//! Many chains, each with its own seed, then give a histogram of final states, which is held to the +//! exact law by Pearson's chi-square and the G-test over every state (states with fewer than five +//! expected chains pooled), and by every cell's marginal and every pair of cells' joint law (z +//! scores, Bonferroni over all of them). The same histogram is also held to the law that has the +//! exact marginals but independent cells: that law must be rejected, which shows the test sees +//! joint structure that per-cell statistics cannot. + +use crate::engine::Engine; +use crate::member::{lowered, plan, run_schedule}; +use crate::port::Port; +use std::collections::HashMap; +use std::fmt::Write as _; +use wai_quantum::quantum_landscape::energy::Landscape; +use wai_quantum::quantum_landscape::sample::{anneal, chain_seed, cuts, out, pick, temperature}; +use wai_quantum::quantum_landscape::settle::Workload; + +const TWO53: f64 = 9_007_199_254_740_992.0; + +/// The state's index: cell 0 is the least significant base-3 digit, −1, 0, +1 as 0, 1, 2. +pub fn index(s: &[i8]) -> usize { + s.iter().rev().fold(0usize, |a, &v| a * 3 + (v + 1) as usize) +} + +pub fn state_of(mut k: usize, n: usize) -> Vec { + (0..n) + .map(|_| { + let v = (k % 3) as i8 - 1; + k /= 3; + v + }) + .collect() +} + +/// The exact law of the final state: sequential sweeps at the inverse temperatures `betas` from +/// `start`, then, with `final_t0`, one synchronous sweep at temperature 0. +pub fn exact_law(w: &Workload, start: &[i8], betas: &[f64], final_t0: bool) -> Vec { + let n = w.n(); + let m = 3usize.pow(n as u32); + let pow3: Vec = (0..n).map(|i| 3usize.pow(i as u32)).collect(); + let mut p = vec![0.0f64; m]; + p[index(start)] = 1.0; + for &beta in betas { + let mut cache: HashMap = HashMap::new(); + for i in 0..n { + let mut q = vec![0.0f64; m]; + for (k, &pk) in p.iter().enumerate() { + if pk == 0.0 { + continue; + } + let s = state_of(k, n); + let h = w.field(&s, i); + let pr = *cache.entry(h).or_insert_with(|| { + let (a, b) = cuts(h, w.cost(), beta); + [a as f64 / TWO53, (b - a) as f64 / TWO53, (TWO53 - b as f64) / TWO53] + }); + let base = k - (s[i] + 1) as usize * pow3[i]; + q[base] += pk * pr[0]; + q[base + pow3[i]] += pk * pr[1]; + q[base + 2 * pow3[i]] += pk * pr[2]; + } + p = q; + } + } + if final_t0 { + let mut q = vec![0.0f64; m]; + for (k, &pk) in p.iter().enumerate() { + if pk != 0.0 { + q[index(&w.sweep(&state_of(k, n)))] += pk; + } + } + p = q; + } + p +} + +/// The reference's chain through the same schedule, draw for draw: cell `i` of sweep `k` takes +/// `out(chain, k·n + i + 1) >> 11`. For the job's anneal this is `sample::anneal` (a test holds the +/// two equal); at a fixed temperature the schedule is the anneal's inner loop with `beta` fixed. +pub fn reference(w: &Workload, start: &[i8], betas: &[f64], final_t0: bool, chain: u64) -> Vec { + let n = w.n(); + let mut s = start.to_vec(); + for (k, &beta) in betas.iter().enumerate() { + let base = (k as u64).wrapping_mul(n as u64); + for i in 0..n { + let (a, b) = cuts(w.field(&s, i), w.cost(), beta); + s[i] = pick(out(chain, base + i as u64 + 1) >> 11, a, b); + } + } + if final_t0 { w.sweep(&s) } else { s } +} + +fn ln_gamma(x: f64) -> f64 { + const G: [f64; 9] = [ + 0.999_999_999_999_809_9, + 676.520_368_121_885_1, + -1_259.139_216_722_402_8, + 771.323_428_777_653_1, + -176.615_029_162_140_6, + 12.507_343_278_686_905, + -0.138_571_095_265_720_12, + 9.984_369_578_019_572e-6, + 1.505_632_735_149_311_6e-7, + ]; + if x < 0.5 { + return (std::f64::consts::PI / (std::f64::consts::PI * x).sin()).ln() - ln_gamma(1.0 - x); + } + let x = x - 1.0; + let mut a = G[0]; + let t = x + 7.5; + for (i, &g) in G.iter().enumerate().skip(1) { + a += g / (x + i as f64); + } + 0.5 * (2.0 * std::f64::consts::PI).ln() + (x + 0.5) * t.ln() - t + a.ln() +} + +/// The upper regularised incomplete gamma function Q(a, x): the chi-square tail is Q(df/2, x/2). +pub fn gamma_q(a: f64, x: f64) -> f64 { + if x <= 0.0 { + return 1.0; + } + let lead = -x + a * x.ln() - ln_gamma(a); + if x < a + 1.0 { + let (mut ap, mut sum, mut del) = (a, 1.0 / a, 1.0 / a); + for _ in 0..1_000_000 { + ap += 1.0; + del *= x / ap; + sum += del; + if del.abs() < sum.abs() * 1e-15 { + break; + } + } + (1.0 - sum * lead.exp()).max(0.0) + } else { + let tiny = 1e-300; + let mut b = x + 1.0 - a; + let mut c = 1.0 / tiny; + let mut d = 1.0 / b; + let mut h = d; + for i in 1..1_000_000 { + let an = -(i as f64) * (i as f64 - a); + b += 2.0; + d = an * d + b; + if d.abs() < tiny { + d = tiny; + } + c = b + an / c; + if c.abs() < tiny { + c = tiny; + } + d = 1.0 / d; + let del = d * c; + h *= del; + if (del - 1.0).abs() < 1e-15 { + break; + } + } + (lead.exp() * h).clamp(0.0, 1.0) + } +} + +/// erfc, to a relative error under 1.2e-7. +pub fn erfc(x: f64) -> f64 { + let z = x.abs(); + let t = 1.0 / (1.0 + 0.5 * z); + let r = t * (-z * z - 1.265_512_23 + + t * (1.000_023_68 + + t * (0.374_091_96 + t * (0.096_784_18 + t * (-0.186_288_06 + t * (0.278_868_07 + t * (-1.135_203_98 + t * (1.488_515_87 + t * (-0.822_152_23 + t * 0.170_872_77))))))))) + .exp(); + if x >= 0.0 { r } else { 2.0 - r } +} + +/// A histogram held to a law. +#[derive(Clone, Debug)] +pub struct Fit { + pub samples: u64, + pub bins: usize, + pub chi2: f64, + pub p_chi2: f64, + pub g: f64, + pub p_g: f64, + /// Total variation distance between the histogram and the law. + pub tv: f64, + /// The largest |z| over every cell's three marginal frequencies, and its Bonferroni p. + pub z_cell: f64, + pub p_cell: f64, + /// The largest |z| over every pair of cells' nine joint frequencies, and its Bonferroni p. + pub z_pair: f64, + pub p_pair: f64, +} + +fn z_max(obs: &[u64], law: &[f64], total: u64, groups: impl Fn(usize) -> Vec, ngroups: usize) -> (f64, usize) { + let mut o = vec![0u64; ngroups]; + let mut e = vec![0.0f64; ngroups]; + for (k, (&c, &p)) in obs.iter().zip(law).enumerate() { + for gidx in groups(k) { + o[gidx] += c; + e[gidx] += p; + } + } + let nt = total as f64; + let mut zmax = 0.0f64; + let mut tests = 0usize; + for (oi, &pi) in o.iter().zip(&e) { + if pi <= 1e-12 || pi >= 1.0 - 1e-12 { + if (*oi > 0 && pi <= 1e-12) || (*oi < total && pi >= 1.0 - 1e-12) { + zmax = f64::INFINITY; + } + continue; + } + tests += 1; + let z = (*oi as f64 - nt * pi) / (nt * pi * (1.0 - pi)).sqrt(); + zmax = zmax.max(z.abs()); + } + (zmax, tests.max(1)) +} + +pub fn fit(counts: &[u64], law: &[f64], n: usize) -> Fit { + let total: u64 = counts.iter().sum(); + let nt = total as f64; + // bins: every state expected at least 5 times on its own, the rest pooled + let (mut chi2, mut g, mut bins) = (0.0, 0.0, 0usize); + let (mut po, mut pe) = (0u64, 0.0f64); + let mut impossible = false; + for (&c, &p) in counts.iter().zip(law) { + let e = nt * p; + if e >= 5.0 { + chi2 += (c as f64 - e).powi(2) / e; + if c > 0 { + g += 2.0 * c as f64 * (c as f64 / e).ln(); + } + bins += 1; + } else { + po += c; + pe += e; + if p == 0.0 && c > 0 { + impossible = true; + } + } + } + if pe > 0.0 { + chi2 += (po as f64 - pe).powi(2) / pe; + if po > 0 { + g += 2.0 * po as f64 * (po as f64 / pe).ln(); + } + bins += 1; + } + let df = bins.saturating_sub(1).max(1) as f64; + let (mut p_chi2, mut p_g) = (gamma_q(df / 2.0, chi2 / 2.0), gamma_q(df / 2.0, g.max(0.0) / 2.0)); + if impossible { + p_chi2 = 0.0; + p_g = 0.0; + } + let tv = 0.5 * counts.iter().zip(law).map(|(&c, &p)| (c as f64 / nt - p).abs()).sum::(); + let pow3: Vec = (0..n).map(|i| 3usize.pow(i as u32)).collect(); + let digit = |k: usize, i: usize| (k / pow3[i]) % 3; + let (z_cell, t_cell) = z_max(counts, law, total, |k| (0..n).map(|i| 3 * i + digit(k, i)).collect(), 3 * n); + let pairs: Vec<(usize, usize)> = (0..n).flat_map(|i| ((i + 1)..n).map(move |j| (i, j))).collect(); + let (z_pair, t_pair) = z_max(counts, law, total, |k| pairs.iter().enumerate().map(|(q, &(i, j))| 9 * q + 3 * digit(k, i) + digit(k, j)).collect(), 9 * pairs.len().max(1)); + let bonf = |z: f64, t: usize| if z.is_infinite() { 0.0 } else { (t as f64 * erfc(z / std::f64::consts::SQRT_2)).min(1.0) }; + Fit { samples: total, bins, chi2, p_chi2, g, p_g, tv, z_cell, p_cell: bonf(z_cell, t_cell), z_pair, p_pair: bonf(z_pair, t_pair) } +} + +/// The law with the same marginals as `law` and independent cells. +pub fn independent(law: &[f64], n: usize) -> Vec { + let mut marg = vec![[0.0f64; 3]; n]; + for (k, &p) in law.iter().enumerate() { + for (i, s) in state_of(k, n).iter().enumerate() { + marg[i][(s + 1) as usize] += p; + } + } + (0..law.len()).map(|k| state_of(k, n).iter().enumerate().map(|(i, s)| marg[i][(s + 1) as usize]).product()).collect() +} + +/// A pre-registered case: a workload, a start, a schedule, a number of chains. +pub struct Case { + pub name: &'static str, + pub landscape: Landscape, + pub start: Vec, + /// Temperatures of the sampled sweeps, in order. + pub temps: Vec, + /// The job's anneal ends with one synchronous sweep at temperature 0. + pub final_t0: bool, + /// For the anneal cases, the job's (t0, sweeps); the reference chain is then `sample::anneal`. + pub anneal: Option<(f64, u32)>, + pub chains: u64, +} + +/// The cases, as `measurements/2026-11-06/JOINT-PREREG.txt` registers them. `scale` multiplies the +/// number of chains (1 on the board; small for the model). +pub fn cases(scale: f64) -> Vec { + let k = |c: u64| ((c as f64 * scale).round() as u64).max(200); + // ring of 8, frustrated by two chords, with fields and a cost + let mut ring = Landscape::new(8); + for i in 0..8 { + ring.couple(i, (i + 1) % 8, if i % 3 == 0 { -1 } else { 1 }); + } + ring.couple(0, 4, -1).couple(2, 6, 1); + ring.set_field(0, 1).set_field(2, -2).set_field(4, 3).set_field(7, -1).set_cost(1); + // dense 6, every pair coupled, signs from a fixed stream + let mut dense = Landscape::new(6); + let mut z = 0x0D_E5E6u64; + for i in 0..6 { + for j in (i + 1)..6 { + z = z.wrapping_add(0x9E37_79B9_7F4A_7C15); + dense.couple(i, j, if out(z, 1) & 1 == 0 { 1 } else { -1 }); + } + } + // sparse 10: a chain with three long couplings and fields + let mut sparse = Landscape::new(10); + for i in 0..9 { + sparse.couple(i, i + 1, if i % 2 == 0 { 1 } else { -1 }); + } + sparse.couple(0, 9, 1).couple(1, 6, -1).couple(3, 8, 1); + sparse.set_field(1, 2).set_field(5, -1).set_field(8, 1).set_cost(1); + let anneal_temps = |t0: f64, sweeps: u32| (0..sweeps - 1).map(|q| temperature(t0, sweeps, q)).collect::>(); + vec![ + Case { name: "ring 8, fixed T = 1.5, 4 sweeps", landscape: ring.clone(), start: vec![0; 8], temps: vec![1.5; 4], final_t0: false, anneal: None, chains: k(200_000) }, + Case { name: "ring 8, the job's anneal from T = 2.5, 10 sweeps", landscape: ring, start: vec![1, -1, 0, 0, 1, -1, 0, 1], temps: anneal_temps(2.5, 10), final_t0: true, anneal: Some((2.5, 10)), chains: k(200_000) }, + Case { name: "dense 6, fixed T = 4, 2 sweeps", landscape: dense, start: vec![1, 1, 1, -1, -1, -1], temps: vec![4.0; 2], final_t0: false, anneal: None, chains: k(200_000) }, + Case { name: "sparse 10, the job's anneal from T = 1.5, 8 sweeps", landscape: sparse, start: vec![0; 10], temps: anneal_temps(1.5, 8), final_t0: true, anneal: Some((1.5, 8)), chains: k(400_000) }, + ] +} + +/// One case's result. +pub struct Outcome { + pub fit: Fit, + pub control: Fit, + /// Chains whose final state equals the reference's, of all. + pub same: u64, +} + +/// Runs a case on an engine: every chain on the device and on the reference, the histogram held to +/// the exact law and to the independent-cells control. Chain `r` has the seed `chain_seed(seed, r)`. +pub fn run_case(e: &mut Engine

, c: &Case, seed: u64) -> Result { + let w = c.landscape.lower(); + let n = w.n(); + let l = lowered(&w); + e.load(&l).map_err(|x| x.0)?; + let segments = plan(&l, &c.temps)?; + let betas: Vec = c.temps.iter().map(|t| 1.0 / t).collect(); + let law = exact_law(&w, &c.start, &betas, c.final_t0); + let mut counts = vec![0u64; law.len()]; + let mut same = 0u64; + let block = 4096u64; + let mut r0 = 0u64; + while r0 < c.chains { + let r1 = (r0 + block).min(c.chains); + let list: Vec<(Vec, u64)> = (r0..r1).map(|r| (c.start.clone(), chain_seed(seed, r))).collect(); + let got = run_schedule(e, &l, &segments, c.final_t0, &list)?; + for (st, (_, ch)) in got.iter().zip(&list) { + counts[index(st)] += 1; + let want = match c.anneal { + Some((t0, sweeps)) => anneal(&w, &c.start, t0, sweeps, *ch).state, + None => reference(&w, &c.start, &betas, c.final_t0, *ch), + }; + if *st == want { + same += 1; + } + } + r0 = r1; + } + let fit_law = fit(&counts, &law, n); + let control = fit(&counts, &independent(&law, n), n); + Ok(Outcome { fit: fit_law, control, same }) +} + +/// Every case, as a report: the pre-registered rule is that each case's chi-square, G, cell and +/// pair p are all at least 0.001 against the exact law, every chain equals the reference's, and, +/// at the registered number of chains (`scale` 1), the independent-cells control is rejected +/// (chi-square p below 1e-6). +pub fn report(e: &mut Engine

, scale: f64, seed: u64) -> Result { + let mut rep = crate::conform::Report::default(); + rep.line(format!("joint statistics against exact enumeration (chains scaled by {scale}, seed {seed})")); + for c in cases(scale) { + let t = std::time::Instant::now(); + let o = run_case(e, &c, seed)?; + let f = &o.fit; + let mut line = String::new(); + let _ = write!( + line, + " {}: {} chains, {} bins; exact law: chi2 {:.1} (p {:.3}), G {:.1} (p {:.3}), TV {:.4}, cell |z| {:.2} (p {:.3}), pair |z| {:.2} (p {:.3}); same as the reference {}/{}; {:.1} s", + c.name, f.samples, f.bins, f.chi2, f.p_chi2, f.g, f.p_g, f.tv, f.z_cell, f.p_cell, f.z_pair, f.p_pair, o.same, f.samples, t.elapsed().as_secs_f64() + ); + rep.line(line); + rep.line(format!( + " control, the same marginals with independent cells: chi2 {:.1} on {} bins (p {:.2e}), cell |z| {:.2} (p {:.3}), pair |z| {:.1} (p {:.2e})", + o.control.chi2, o.control.bins, o.control.p_chi2, o.control.z_cell, o.control.p_cell, o.control.z_pair, o.control.p_pair + )); + let ok = f.p_chi2 >= 1e-3 && f.p_g >= 1e-3 && f.p_cell >= 1e-3 && f.p_pair >= 1e-3; + rep.check(ok, format!("{}: the joint law is the exact law (every p at least 0.001)", c.name)); + rep.check(o.same == f.samples, format!("{}: every chain's final state is the reference's", c.name)); + if scale >= 1.0 { + rep.check(o.control.p_chi2 < 1e-6, format!("{}: the independent-cells control is rejected (chi-square p below 1e-6)", c.name)); + } + } + Ok(rep) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn index_round_trips() { + for k in 0..243 { + assert_eq!(index(&state_of(k, 5)), k); + } + } + + #[test] + fn the_law_sums_to_one_and_the_schedule_is_the_anneal() { + for c in cases(0.0) { + let w = c.landscape.lower(); + let betas: Vec = c.temps.iter().map(|t| 1.0 / t).collect(); + let law = exact_law(&w, &c.start, &betas, c.final_t0); + let s: f64 = law.iter().sum(); + assert!((s - 1.0).abs() < 1e-12, "{}: {s}", c.name); + if let Some((t0, sweeps)) = c.anneal { + for r in 0..50 { + let ch = chain_seed(9, r); + assert_eq!(reference(&w, &c.start, &betas, true, ch), anneal(&w, &c.start, t0, sweeps, ch).state, "{}", c.name); + } + } + } + } + + #[test] + fn the_reference_meets_the_exact_law_and_the_control_fails() { + // the dense case on the CPU reference: 100,000 chains + let c = cases(0.5).remove(2); + let w = c.landscape.lower(); + let betas: Vec = c.temps.iter().map(|t| 1.0 / t).collect(); + let law = exact_law(&w, &c.start, &betas, false); + let mut counts = vec![0u64; law.len()]; + for r in 0..c.chains { + counts[index(&reference(&w, &c.start, &betas, false, chain_seed(3, r)))] += 1; + } + let f = fit(&counts, &law, w.n()); + assert!(f.p_chi2 > 1e-3 && f.p_g > 1e-3 && f.p_cell > 1e-3 && f.p_pair > 1e-3, "{f:?}"); + let ctl = fit(&counts, &independent(&law, w.n()), w.n()); + assert!(ctl.p_chi2 < 1e-6, "{ctl:?}"); + assert!(ctl.p_cell > 1e-3, "the control has the right marginals: {ctl:?}"); + } + + #[test] + fn the_tails_are_right() { + // chi-square with 10 degrees of freedom: P(X > 18.307) = 0.05; with 1000: P(X > 1074.68) = 0.05 + assert!((gamma_q(5.0, 18.307 / 2.0) - 0.05).abs() < 1e-4); + assert!((gamma_q(500.0, 1074.68 / 2.0) - 0.05).abs() < 2e-3); + assert!((erfc(1.959_964 / std::f64::consts::SQRT_2) - 0.05).abs() < 1e-6); + } +} diff --git a/wai/wai-quantum-landscape-fpga/src/latency.rs b/wai/wai-quantum-landscape-fpga/src/latency.rs new file mode 100644 index 000000000..6ca5e5d2e --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/src/latency.rs @@ -0,0 +1,198 @@ +//! The processor-to-logic latency, measured on the board: no primary source gives it for this part, +//! so it is timed here. Each access is timed alone with the processor's generic timer (CNTVCT_EL0, +//! read behind an ISB), many times, and reported as a distribution with the timer's own cost beside +//! it; the bulk paths (a DMA list, a batch of chains) are timed end to end. + +use crate::engine::Engine; +use crate::exact::cases; +use crate::member::{lowered, plan, run_schedule}; +use crate::port::Port; +use crate::regs::*; +use std::fmt::Write as _; +use std::time::Instant; +use wai_quantum::quantum_landscape::sample::{chain_seed, cuts}; + +#[cfg(target_arch = "aarch64")] +#[inline(always)] +pub fn ticks() -> u64 { + let t: u64; + unsafe { core::arch::asm!("isb", "mrs {0}, cntvct_el0", out(reg) t) }; + t +} +#[cfg(target_arch = "aarch64")] +pub fn tick_hz() -> f64 { + let f: u64; + unsafe { core::arch::asm!("mrs {0}, cntfrq_el0", out(reg) f) }; + f as f64 +} +#[cfg(not(target_arch = "aarch64"))] +pub fn ticks() -> u64 { + use std::sync::OnceLock; + static T0: OnceLock = OnceLock::new(); + T0.get_or_init(Instant::now).elapsed().as_nanos() as u64 +} +#[cfg(not(target_arch = "aarch64"))] +pub fn tick_hz() -> f64 { + 1e9 +} + +/// A distribution of times, in nanoseconds. +pub struct Dist(pub Vec); + +impl Dist { + fn q(&self, f: f64) -> f64 { + let mut v = self.0.clone(); + v.sort_by(|a, b| a.partial_cmp(b).unwrap()); + v[((v.len() - 1) as f64 * f).round() as usize] + } + pub fn line(&self, what: &str) -> String { + let mean = self.0.iter().sum::() / self.0.len() as f64; + format!( + " {what:58} median {:7.0} ns p10 {:7.0} p90 {:7.0} p99 {:7.0} max {:8.0} mean {:7.0} (n {})", + self.q(0.5), + self.q(0.1), + self.q(0.9), + self.q(0.99), + self.q(1.0), + mean, + self.0.len() + ) + } +} + +/// Times `f` alone, `n` times, after `n / 10` unrecorded warm-up calls. +pub fn time_each(n: usize, mut f: impl FnMut()) -> Dist { + let ns = 1e9 / tick_hz(); + for _ in 0..n / 10 { + f(); + } + let mut v = Vec::with_capacity(n); + for _ in 0..n { + let a = ticks(); + f(); + let b = ticks(); + v.push((b - a) as f64 * ns); + } + Dist(v) +} + +/// The header every latency report starts with. +pub fn header(link: &str) -> String { + let mut s = String::new(); + let _ = writeln!(s, "processor-to-logic latency, {link} link: one board, the engine at its measured clock"); + let _ = writeln!(s, " timer: CNTVCT_EL0 at {:.3} MHz, one tick {:.1} ns", tick_hz() / 1e6, 1e9 / tick_hz()); + s.push_str(&time_each(20_000, || {}).line("the timer alone (two reads, an ISB each)")); + s.push('\n'); + s +} + +/// A 64-level table of 128 fields each, the largest load a job makes: 32,832 register writes. +fn big_table(beta0: f64) -> Vec> { + (0..64).map(|l| (-64..64).map(|h| cuts(h, 1, beta0 + 0.01 * l as f64)).collect()).collect() +} + +/// End-to-end timings that every link has: a full table load, and chains run as a batch. +pub fn bulk_paths(e: &mut Engine

, label: &str, chains: u64) -> Result { + let mut s = String::new(); + let (ta, tb) = (big_table(0.2), big_table(0.3)); + let mut times = Vec::new(); + for k in 0..6 { + let t = Instant::now(); + e.load_table(-64, 7, -64, if k % 2 == 0 { &ta } else { &tb }).map_err(|x| x.0)?; + times.push(t.elapsed().as_secs_f64()); + } + times.sort_by(|a, b| a.partial_cmp(b).unwrap()); + let writes = 64 * (1 + 4 * 128) + 3; + let _ = writeln!(s, " {label}: a full table (64 levels x 128 fields, {writes} register writes): median {:.2} ms of 6, {:.0} ns a write", times[3] * 1e3, times[3] * 1e9 / writes as f64); + // read one entry back: the load is real + let probe = e.table_entry(5 * 128 + 70).map_err(|x| x.0)?; + if probe != tb[5][70] { + return Err(format!("{label}: the table reads back {probe:?}, want {:?}", tb[5][70])); + } + let c = cases(1.0).remove(0); + let w = c.landscape.lower(); + let l = lowered(&w); + e.load(&l).map_err(|x| x.0)?; + let seg = plan(&l, &c.temps)?; + let list: Vec<(Vec, u64)> = (0..chains).map(|r| (c.start.clone(), chain_seed(11, r))).collect(); + run_schedule(e, &l, &seg, false, &list[..list.len().min(256)])?; + let t = Instant::now(); + run_schedule(e, &l, &seg, false, &list)?; + let dt = t.elapsed().as_secs_f64(); + let _ = writeln!( + s, + " {label}: {chains} chains of {} cells x {} sweeps: {:.3} s, {:.2} us a chain, {:.0} chains/s", + l.n, + c.temps.len(), + dt, + dt * 1e6 / chains as f64, + chains as f64 / dt + ); + Ok(s) +} + +#[cfg(unix)] +pub fn axi(p: &mut crate::axi::AxiPort, n: usize) -> String { + use crate::axi::*; + let mut s = header("AXI"); + s.push_str(&time_each(n, || { + std::hint::black_box(p.signature()); + }) + .line("EMIO signature read (a processor GPIO register)")); + s.push('\n'); + s.push_str(&time_each(n, || { + std::hint::black_box(p.rd_raw(WRAP_ID)); + }) + .line("AXI-Lite read, a link register")); + s.push('\n'); + s.push_str(&time_each(n, || { + std::hint::black_box(p.rd_raw(ID)); + }) + .line("AXI-Lite read, an engine register")); + s.push('\n'); + s.push_str(&time_each(n, || p.wr_raw(DMA_COUNT, 0)).line("AXI-Lite write, a link register")); + s.push('\n'); + s.push_str(&time_each(n, || p.wr_raw(SEL, 0)).line("AXI-Lite write, an engine register")); + s.push('\n'); + s.push_str(&time_each(n, || { + p.wr_raw(SEL, 0); + std::hint::black_box(p.rd_raw(FIELD)); + }) + .line("AXI-Lite write then read (select a cell, read its field)")); + s.push('\n'); + s.push_str(&time_each(n, || { + std::hint::black_box(p.rd(ID).ok()); + }) + .line("guarded read, as every job access goes (signature, then read)")); + s.push('\n'); + let one = [(SEL, 0u32)]; + s.push_str(&time_each(n / 20, || { + p.dma_list(&one, std::time::Duration::from_secs(1)).expect("DMA"); + }) + .line("a 1-entry write list by DMA, kick to done (4 writes, polls)")); + s.push('\n'); + let page: Vec<(u16, u32)> = (0..512).map(|k| (SEL, k as u32 & 63)).collect(); + let d = time_each(n / 50, || { + p.dma_list(&page, std::time::Duration::from_secs(1)).expect("DMA"); + }); + let med = d.q(0.5); + s.push_str(&d.line("a 512-entry write list by DMA (one page), kick to done")); + let _ = writeln!(s, "\n = {:.1} ns an entry, {:.1} M register writes/s", med / 512.0, 512.0 / med * 1e3); + s +} + +#[cfg(unix)] +pub fn emio(p: &mut crate::emio::EmioPort, n: usize) -> String { + let mut s = header("EMIO"); + s.push_str(&time_each(n, || { + std::hint::black_box(p.read(ID).ok()); + }) + .line("EMIO register read (handshake over GPIO)")); + s.push('\n'); + s.push_str(&time_each(n, || { + p.write(SEL, 0).ok(); + }) + .line("EMIO register write (handshake over GPIO)")); + s.push('\n'); + s +} diff --git a/wai/wai-quantum-landscape-fpga/src/lib.rs b/wai/wai-quantum-landscape-fpga/src/lib.rs index 89ad353e4..b64b3ac7b 100644 --- a/wai/wai-quantum-landscape-fpga/src/lib.rs +++ b/wai/wai-quantum-landscape-fpga/src/lib.rs @@ -4,8 +4,11 @@ //! and meters its power. Classical throughout: three-state cells and a counter-based random //! stream, no quantum effect. +pub mod axi; pub mod conform; pub mod engine; +pub mod exact; +pub mod latency; pub mod member; pub mod model; pub mod port; diff --git a/wai/wai-quantum-landscape-fpga/src/main.rs b/wai/wai-quantum-landscape-fpga/src/main.rs index b15e1bd17..c087fa059 100644 --- a/wai/wai-quantum-landscape-fpga/src/main.rs +++ b/wai/wai-quantum-landscape-fpga/src/main.rs @@ -174,6 +174,15 @@ fn on_board(cmd: &str, args: &[String], port: P) { }; println!("{}", serde_json::to_string_pretty(&out).unwrap()); } + "joint" => { + let mut e = m.engine.borrow_mut(); + let rep = wai_quantum_landscape_fpga::exact::report(&mut e, flag(args, "--scale").unwrap_or(1.0), flag(args, "--seed").unwrap_or(2026)).unwrap_or_else(|x| die(x)); + drop(e); + let ok = m.engine.borrow_mut().port.read(ID).map(|v| v == ID_VALUE).unwrap_or(false); + let mut rep = rep; + rep.check(ok, "the engine still answers with its ID at the end: no other session replaced it"); + finish(&rep) + } "status" => { let mut e = m.engine.borrow_mut(); let t0 = std::time::Instant::now(); @@ -239,6 +248,39 @@ fn meter_class(e: &mut Engine

, class: &str, plan_m: &wai_quantum_lan wai_quantum_landscape_fpga::power::meter(e, plan_m, &mut start, log).unwrap_or_else(|x| die(x)) } +#[cfg(unix)] +enum Link { + Axi(wai_quantum_landscape_fpga::axi::AxiPort), + Emio(wai_quantum_landscape_fpga::emio::EmioPort), +} + +/// The loaded design's link: AXI if its signature is on EMIO bank 3 (a GPIO read, safe whatever is +/// loaded), else EMIO; `--link` insists on one. +#[cfg(unix)] +fn open_link(args: &[String]) -> Link { + let axi = match flag::(args, "--link").as_deref() { + Some("axi") => true, + Some("emio") => false, + Some(o) => die(format!("--link {o}: axi or emio")), + None => wai_quantum_landscape_fpga::axi::signature_present(), + }; + if axi { + Link::Axi(wai_quantum_landscape_fpga::axi::AxiPort::open().unwrap_or_else(|e| die(e))) + } else { + Link::Emio(wai_quantum_landscape_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e))) + } +} + +#[cfg(unix)] +macro_rules! with_link { + ($args:expr, $p:ident => $body:expr) => { + match open_link($args) { + Link::Axi($p) => $body, + Link::Emio($p) => $body, + } + }; +} + fn main() { let args: Vec = std::env::args().skip(1).collect(); let cmd = args.first().map(|s| s.as_str()).unwrap_or_else(|| usage()); @@ -247,7 +289,8 @@ fn main() { let scale = flag(&args, "--scale").unwrap_or(0.02); let m = model_member(); let mut rep = conform::full(&m, scale).unwrap_or_else(|e| die(e)); - for r in [conform::fields(&m, 4).unwrap_or_else(|e| die(e)), conform::agree(&m, flag(&args, "--seeds").unwrap_or(3)).unwrap_or_else(|e| die(e))] { + let joint = wai_quantum_landscape_fpga::exact::report(&mut m.engine.borrow_mut(), 0.002, 2026).unwrap_or_else(|e| die(e)); + for r in [conform::fields(&m, 4).unwrap_or_else(|e| die(e)), conform::agree(&m, flag(&args, "--seeds").unwrap_or(3)).unwrap_or_else(|e| die(e)), joint] { rep.text += &r.text; rep.checks += r.checks; rep.failures.extend(r.failures); @@ -256,12 +299,18 @@ fn main() { } "gen-tb" => { let out = args.get(1).unwrap_or_else(|| usage()); - let m = BoardMember::new(Engine::open(ScriptPort::new()).unwrap_or_else(|e| die(e)), BoardInfo::default()); + let port = if args.iter().any(|a| a == "--axi") { ScriptPort::axi() } else { ScriptPort::new() }; + let m = BoardMember::new(Engine::open(port).unwrap_or_else(|e| die(e)), BoardInfo::default()); conform::tb_session(&m).unwrap_or_else(|e| die(e)); let e = m.engine.borrow(); std::fs::write(out, &e.port.text).unwrap_or_else(|e| die(e)); println!("{} lines, {} checked reads -> {out}", e.port.text.lines().count(), e.port.reads); } + "joint" if args.iter().any(|a| a == "--model") => { + let mut e = Engine::open(ModelPort::new()).unwrap_or_else(|e| die(e)); + let rep = wai_quantum_landscape_fpga::exact::report(&mut e, flag(&args, "--scale").unwrap_or(1.0), flag(&args, "--seed").unwrap_or(2026)).unwrap_or_else(|e| die(e)); + finish(&rep) + } "analyze" => { let f = args.get(1).unwrap_or_else(|| usage()); let text = std::fs::read_to_string(f).unwrap_or_else(|e| die(e)); @@ -277,9 +326,8 @@ fn main() { let f = args.get(1).unwrap_or_else(|| usage()); println!("{}", wai_quantum_landscape_fpga::board::load_bitstream(f).unwrap_or_else(|e| die(e))); let _l = wai_quantum_landscape_fpga::board::lock().unwrap_or_else(|e| die(e)); - let port = wai_quantum_landscape_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e)); - Engine::open(port).unwrap_or_else(|e| die(e)); - println!("the engine answers with its ID and version"); + let link = with_link!(&args, p => { let name = p.name(); Engine::open(p).unwrap_or_else(|e| die(e)); name }); + println!("the engine answers with its ID and version, over its {link} link"); } #[cfg(unix)] "release" => { @@ -290,14 +338,16 @@ fn main() { "clock" => { let d: u32 = args.get(1).and_then(|v| v.parse().ok()).unwrap_or_else(|| usage()); let _l = wai_quantum_landscape_fpga::board::lock().unwrap_or_else(|e| die(e)); - let mut e = Engine::open(wai_quantum_landscape_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e))).unwrap_or_else(|e| die(e)); - let old = wai_quantum_landscape_fpga::board::set_pl0_div(d).unwrap_or_else(|e| die(e)); - std::thread::sleep(std::time::Duration::from_millis(100)); - let t0 = std::time::Instant::now(); - let u0 = e.counter(UPTIME).unwrap_or_else(|e| die(e)); - std::thread::sleep(std::time::Duration::from_secs(1)); - let u1 = e.counter(UPTIME).unwrap_or_else(|e| die(e)); - println!("PL0_REF_CTRL was {old:#x}; nominal {:.2} MHz; measured {:.4} MHz", 1500.0 / f64::from(d), (u1 - u0) as f64 / t0.elapsed().as_secs_f64() / 1e6); + with_link!(&args, p => { + let mut e = Engine::open(p).unwrap_or_else(|e| die(e)); + let old = wai_quantum_landscape_fpga::board::set_pl0_div(d).unwrap_or_else(|e| die(e)); + std::thread::sleep(std::time::Duration::from_millis(100)); + let t0 = std::time::Instant::now(); + let u0 = e.counter(UPTIME).unwrap_or_else(|e| die(e)); + std::thread::sleep(std::time::Duration::from_secs(1)); + let u1 = e.counter(UPTIME).unwrap_or_else(|e| die(e)); + println!("PL0_REF_CTRL was {old:#x}; nominal {:.2} MHz; measured {:.4} MHz", 1500.0 / f64::from(d), (u1 - u0) as f64 / t0.elapsed().as_secs_f64() / 1e6); + }); } #[cfg(unix)] "meter" => { @@ -308,28 +358,52 @@ fn main() { let plan_m = wai_quantum_landscape_fpga::power::Plan { passes, settle_s, samples, null: args.iter().any(|a| a == "--null") }; let cores: u16 = flag(&args, "--cores").unwrap_or(1); let _l = wai_quantum_landscape_fpga::board::lock().unwrap_or_else(|e| die(e)); - let port = wai_quantum_landscape_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e)); let mut log = String::new(); - let _ = writeln!(log, "# class {class} cores {cores}"); - let pts = if cores > 1 { - let mut e = Engine::open(wai_quantum_landscape_fpga::port::BroadcastPort { inner: port, k: cores }).unwrap_or_else(|x| die(x)); - meter_class(&mut e, &class, &plan_m, &mut log) - } else { - let mut e = Engine::open(port).unwrap_or_else(|x| die(x)); - meter_class(&mut e, &class, &plan_m, &mut log) - }; + let pts = with_link!(&args, port => { + let _ = writeln!(log, "# class {class} cores {cores} link {}", port.name()); + if cores > 1 { + let mut e = Engine::open(wai_quantum_landscape_fpga::port::BroadcastPort { inner: port, k: cores }).unwrap_or_else(|x| die(x)); + meter_class(&mut e, &class, &plan_m, &mut log) + } else { + let mut e = Engine::open(port).unwrap_or_else(|x| die(x)); + meter_class(&mut e, &class, &plan_m, &mut log) + } + }); print!("{log}"); for l in wai_quantum_landscape_fpga::power::summarise(&pts, flag(&args, "--predict")).text.lines() { println!("# {l}"); } } #[cfg(unix)] - "conform" | "agree" | "landscape" | "status" | "throughput" => { + "latency" => { + let n: usize = args.get(1).and_then(|v| v.parse().ok()).unwrap_or(100_000); let _l = wai_quantum_landscape_fpga::board::lock().unwrap_or_else(|e| die(e)); - let mut port = wai_quantum_landscape_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e)); - match flag::(&args, "--core") { - Some(core) => on_board(cmd, &args, wai_quantum_landscape_fpga::port::CorePort { inner: &mut port, core }), - None => on_board(cmd, &args, port), + use wai_quantum_landscape_fpga::latency; + match open_link(&args) { + Link::Axi(mut p) => { + print!("{}", latency::axi(&mut p, n)); + let mut e = Engine::open(p).unwrap_or_else(|x| die(x)); + print!("{}", latency::bulk_paths(&mut e, "AXI, DMA and sequencer", 65_536).unwrap_or_else(|x| die(x))); + e.port.sequencer = false; + e.port.dma_min = usize::MAX; + print!("{}", latency::bulk_paths(&mut e, "AXI, plain register accesses", 16_384).unwrap_or_else(|x| die(x))); + println!(" AXI accesses {}, DMA lists {}, sequencer batches {}", e.port.accesses, e.port.dma_lists, e.port.batches); + } + Link::Emio(mut p) => { + print!("{}", latency::emio(&mut p, n / 4)); + let mut e = Engine::open(p).unwrap_or_else(|x| die(x)); + print!("{}", latency::bulk_paths(&mut e, "EMIO, plain register accesses", 4_096).unwrap_or_else(|x| die(x))); + } + } + } + #[cfg(unix)] + "conform" | "agree" | "landscape" | "status" | "throughput" | "joint" => { + let _l = wai_quantum_landscape_fpga::board::lock().unwrap_or_else(|e| die(e)); + match (open_link(&args), flag::(&args, "--core")) { + (Link::Emio(mut port), Some(core)) => on_board(cmd, &args, wai_quantum_landscape_fpga::port::CorePort { inner: &mut port, core }), + (Link::Axi(_), Some(_)) => die("--core is for the multi-core EMIO build"), + (Link::Axi(p), None) => on_board(cmd, &args, p), + (Link::Emio(p), None) => on_board(cmd, &args, p), } } _ => usage(), diff --git a/wai/wai-quantum-landscape-fpga/src/member.rs b/wai/wai-quantum-landscape-fpga/src/member.rs index dc05c7d63..d8b835249 100644 --- a/wai/wai-quantum-landscape-fpga/src/member.rs +++ b/wai/wai-quantum-landscape-fpga/src/member.rs @@ -14,10 +14,12 @@ //! at any of the segment's temperatures, and refuses a job for which no window fits. The `chains` //! vectors hold the result to the reference's bits. //! -//! A run longer than the table holds goes in segments: the table is rewritten and the run continued -//! with the draws running on. A chain at temperature 0 runs every sweep, as the reference's does: -//! the engine stops at a fixed point or a two-cycle, and past a two-cycle the state alternates, so -//! one more sweep is taken when the count left is odd. +//! The chains of a job run as one batch ([`Engine::run_batch`]): on the AXI link the engine's +//! sequencer runs them one after another in fabric. A run longer than the table holds goes in +//! segments: the table is rewritten, and every chain starts the next segment from where it ended +//! with its draw counter at the draw the reference takes next. A chain at temperature 0 runs every +//! sweep, as the reference's does: the engine stops at a fixed point or a two-cycle, and past a +//! two-cycle the state alternates, so one more sweep is taken when the count left is odd. //! //! What it has earned is not assumed: [`BoardMember::earn`] runs the `settle` and `chains` vectors //! on the device, and a kernel that misses one bit is `uncertified`. @@ -137,6 +139,26 @@ pub fn plan(l: &Loaded, temps: &[f64]) -> Result, String> { Ok(out) } +/// Sampling chains through a schedule planned by [`plan`]: segment by segment, every chain through +/// each, a segment starting from where the chain left the last one with its draw counter at the +/// draw the reference takes next (`out(seed, k·n + i + 1)` for cell `i` of sweep `k`). With +/// `final_t0`, one synchronous sweep at temperature 0 ends the last segment. `chains` are (start, +/// seed); the result is each chain's final state. +pub fn run_schedule(e: &mut Engine

, l: &Loaded, segments: &[Segment], final_t0: bool, chains: &[(Vec, u64)]) -> Result>, String> { + let mut states: Vec> = chains.iter().map(|c| c.0.clone()).collect(); + let mut done = 0u64; + for (s, seg) in segments.iter().enumerate() { + let last = s + 1 == segments.len(); + e.load_table(seg.win_lo, seg.wbits, seg.first, &seg.levels).map_err(|e| e.0)?; + let first_draw = done * l.n as u64 + 1; + let list: Vec<(Vec, u64)> = chains.iter().zip(&states).map(|(c, st)| (st.clone(), c.1.wrapping_add(GOLDEN.wrapping_mul(first_draw)))).collect(); + let runs = e.run_batch(MODE_GIBBS, seg.sweeps as u32, 1, last && final_t0, &list).map_err(|e| e.0)?; + states = runs.into_iter().map(|r| r.state).collect(); + done += seg.sweeps as u64; + } + Ok(states) +} + impl BoardMember

{ pub fn new(engine: Engine

, info: BoardInfo) -> Self { BoardMember { engine: RefCell::new(engine), info, settle_parity: Parity::Uncertified, sample_parity: Parity::Uncertified } @@ -157,19 +179,6 @@ impl BoardMember

{ Ok(l) } - /// The chain at temperature 0, every sweep run. - fn quench(&self, w: &Workload, start: &[i8], sweeps: u32) -> Result, String> { - if sweeps == 0 { - return Ok(start.to_vec()); - } - let mut e = self.engine.borrow_mut(); - let r = e.run_t0(start, sweeps).map_err(|e| e.0)?; - if r.term == TERM_2C && (sweeps - r.steps) % 2 == 1 { - return Ok(e.run_t0(&r.state, 1).map_err(|e| e.0)?.state); - } - let _ = w; - Ok(r.state) - } } impl Member for BoardMember

{ @@ -216,33 +225,38 @@ impl Member for BoardMember

{ return Err("a sweep before the last came out at temperature 0".into()); } let segments = if hot > 0 { plan(&l, &temps)? } else { Vec::new() }; - let mut out = Vec::with_capacity(chains.len()); - for c in chains { - if c.start.len() != n { - return Err(format!("a start of {} cells for {n} variables", c.start.len())); - } - let state = if hot == 0 { - self.quench(w, &c.start, sweeps)? - } else { - let mut e = self.engine.borrow_mut(); - e.chain(c.seed).map_err(|e| e.0)?; - let mut state = Vec::new(); - for (s, seg) in segments.iter().enumerate() { - let last = s + 1 == segments.len(); - e.load_table(seg.win_lo, seg.wbits, seg.first, &seg.levels).map_err(|e| e.0)?; - let r = if s == 0 { - e.run_sweeps(MODE_GIBBS, &c.start, seg.sweeps as u32, 1, last) - } else { - e.cont_sweeps(MODE_GIBBS, seg.sweeps as u32, 1, last) - } - .map_err(|e| e.0)?; - state = r.state; - } - state - }; - let term = if w.rests(&state) { Term::FixedPoint } else { Term::Bound }; - out.push(Outcome { state, sweeps, term, accumulates: u64::from(sweeps) * l.nonzeros(), draws: n as u64 * u64::from(hot) }); + if let Some(c) = chains.iter().find(|c| c.start.len() != n) { + return Err(format!("a start of {} cells for {n} variables", c.start.len())); } - Ok(out) + let mut states: Vec> = chains.iter().map(|c| c.start.clone()).collect(); + let mut e = self.engine.borrow_mut(); + if sweeps > 0 && hot == 0 { + // at temperature 0 every sweep is run: the engine stops at a fixed point or a two-cycle, + // and past a two-cycle the state alternates, so one more sweep where the count left is odd + let runs = e.run_batch(MODE_T0, sweeps, 1, false, &states.iter().map(|s| (s.clone(), 0)).collect::>()).map_err(|e| e.0)?; + let mut odd = Vec::new(); + for (k, r) in runs.into_iter().enumerate() { + if r.term == TERM_2C && (sweeps - r.steps) % 2 == 1 { + odd.push(k); + } + states[k] = r.state; + } + if !odd.is_empty() { + let more = e.run_batch(MODE_T0, 1, 1, false, &odd.iter().map(|&k| (states[k].clone(), 0)).collect::>()).map_err(|e| e.0)?; + for (&k, r) in odd.iter().zip(more) { + states[k] = r.state; + } + } + } else if hot > 0 { + let list: Vec<(Vec, u64)> = chains.iter().map(|c| (c.start.clone(), c.seed)).collect(); + states = run_schedule(&mut e, &l, &segments, true, &list)?; + } + Ok(states + .into_iter() + .map(|state| { + let term = if w.rests(&state) { Term::FixedPoint } else { Term::Bound }; + Outcome { state, sweeps, term, accumulates: u64::from(sweeps) * l.nonzeros(), draws: n as u64 * u64::from(hot) } + }) + .collect()) } } diff --git a/wai/wai-quantum-landscape-fpga/src/port.rs b/wai/wai-quantum-landscape-fpga/src/port.rs index 6834abd26..7153442ed 100644 --- a/wai/wai-quantum-landscape-fpga/src/port.rs +++ b/wai/wai-quantum-landscape-fpga/src/port.rs @@ -1,9 +1,21 @@ -//! A register port. The board's is the EMIO lines (`emio.rs`); the model's is software (`model.rs`); -//! the testbench script records one (`script.rs`). Everything above this trait is shared by all three. +//! A register port. The board's is the AXI link (`axi.rs`) or the EMIO lines (`emio.rs`); the +//! model's is software (`model.rs`); the testbench script records one (`script.rs`). Everything +//! above this trait is shared by all of them. +//! +//! Two operations have a plain meaning in register accesses and a faster one on the AXI link: +//! [`Port::bulk`], a list of writes in order (the AXI link reads it from DDR by DMA), and +//! [`Port::batch`], a run of chains (the AXI link's sequencer runs them in fabric). The defaults +//! here are the plain meaning, and the fast forms must give the same bits. +use crate::regs::{BOUNDARY, CONTROL, CTL_START, STATE, STATUS, STEPS, X0_HI, X0_LO}; use std::fmt; use std::time::Duration; +/// One chain for [`Port::batch`]: BOUNDARY 0-3, X0_LO, X0_HI. +pub type ChainWords = [u32; 6]; +/// What a chain leaves: STATE 0-3, STEPS, STATUS. +pub type ResultWords = [u32; 6]; + #[derive(Debug, Clone, PartialEq)] pub struct Error(pub String); impl fmt::Display for Error { @@ -21,6 +33,47 @@ pub trait Port { fn read(&mut self, addr: u16) -> Result; /// Poll until the engine is idle. Errs on timeout rather than waiting forever. fn wait_idle(&mut self, timeout: Duration) -> Result<(), Error>; + + /// Writes, in order. + fn bulk(&mut self, list: &[(u16, u32)]) -> Result<(), Error> { + bulk_plain(self, list) + } + + /// For each chain: its start and draw counter written, the job started with the mode, bound and + /// table already set, waited for, and its state, sweeps and status read back. + fn batch(&mut self, chains: &[ChainWords], timeout: Duration) -> Result, Error> { + batch_plain(self, chains, timeout) + } +} + +/// [`Port::bulk`] as plain register writes. +pub fn bulk_plain(p: &mut P, list: &[(u16, u32)]) -> Result<(), Error> { + for &(a, v) in list { + p.write(a, v)?; + } + Ok(()) +} + +/// [`Port::batch`] as plain register accesses: what the AXI link's sequencer does, from the host. +pub fn batch_plain(p: &mut P, chains: &[ChainWords], timeout: Duration) -> Result, Error> { + let mut out = Vec::with_capacity(chains.len()); + for c in chains { + for (k, &w) in c[..4].iter().enumerate() { + p.write(BOUNDARY + k as u16, w)?; + } + p.write(X0_LO, c[4])?; + p.write(X0_HI, c[5])?; + p.write(CONTROL, CTL_START)?; + p.wait_idle(timeout)?; + let mut r = [0u32; 6]; + for (k, w) in r[..4].iter_mut().enumerate() { + *w = p.read(STATE + k as u16)?; + } + r[4] = p.read(STEPS)?; + r[5] = p.read(STATUS)?; + out.push(r); + } + Ok(out) } /// One core of the multi-core bitstream (`rtl/landscape_multi.v`): address bits 15:13 pick it. @@ -73,4 +126,10 @@ impl Port for &mut P { fn wait_idle(&mut self, timeout: Duration) -> Result<(), Error> { (**self).wait_idle(timeout) } + fn bulk(&mut self, list: &[(u16, u32)]) -> Result<(), Error> { + (**self).bulk(list) + } + fn batch(&mut self, chains: &[ChainWords], timeout: Duration) -> Result, Error> { + (**self).batch(chains, timeout) + } } diff --git a/wai/wai-quantum-landscape-fpga/src/script.rs b/wai/wai-quantum-landscape-fpga/src/script.rs index 9d90dd4da..018546b2b 100644 --- a/wai/wai-quantum-landscape-fpga/src/script.rs +++ b/wai/wai-quantum-landscape-fpga/src/script.rs @@ -4,9 +4,20 @@ //! W aaaa dddddddd write //! R aaaa dddddddd mmmmmmmm read, expect (value & mask) == (d & mask) //! I wait until not busy +//! +//! In AXI form (`tb/tb_landscape_axi.v`) a bulk list and a batch of chains go as the AXI link +//! carries them, so the testbench drives the DMA and the sequencer: +//! +//! D n the next n lines, `aaaa dddddddd`, are a write list: put it in memory +//! and run the DMA over it +//! Q n run the sequencer over n chains (their words already in the chain RAM) +//! and wait for it +//! +//! and a batch's results are checked as reads of the result RAM. +use crate::axi::{CHAIN_RAM, RESULT_RAM, WRAP_DROPS}; use crate::model::ModelPort; -use crate::port::{Error, Port}; +use crate::port::{ChainWords, Error, Port, ResultWords}; use crate::regs; use std::fmt::Write as _; use std::time::Duration; @@ -15,11 +26,20 @@ pub struct ScriptPort { pub model: ModelPort, pub text: String, pub reads: usize, + /// Record bulk lists and batches in the AXI form. + pub axi: bool, } impl ScriptPort { pub fn new() -> Self { - ScriptPort { model: ModelPort::new(), text: String::new(), reads: 0 } + ScriptPort { model: ModelPort::new(), text: String::new(), reads: 0, axi: false } + } + pub fn axi() -> Self { + ScriptPort { axi: true, ..Self::new() } + } + fn expect(&mut self, addr: u16, v: u32, mask: u32) { + let _ = writeln!(self.text, "R {addr:04x} {v:08x} {mask:08x}"); + self.reads += 1; } } @@ -39,12 +59,47 @@ impl Port for ScriptPort { let a = addr & 0x1FFF; // uptime counts real clocks, which the model cannot know let mask = if a == regs::UPTIME || a == regs::UPTIME + 1 { 0u32 } else { 0xFFFF_FFFFu32 }; - let _ = writeln!(self.text, "R {addr:04x} {v:08x} {mask:08x}"); - self.reads += 1; + self.expect(addr, v, mask); Ok(v) } fn wait_idle(&mut self, t: Duration) -> Result<(), Error> { self.text.push_str("I\n"); self.model.wait_idle(t) } + fn bulk(&mut self, list: &[(u16, u32)]) -> Result<(), Error> { + if !self.axi { + return crate::port::bulk_plain(self, list); + } + let _ = writeln!(self.text, "D {}", list.len()); + for &(a, v) in list { + let _ = writeln!(self.text, "{a:04x} {v:08x}"); + self.model.write(a & 0x1FFF, v)?; + } + Ok(()) + } + fn batch(&mut self, chains: &[ChainWords], t: Duration) -> Result, Error> { + if !self.axi { + return crate::port::batch_plain(self, chains, t); + } + let mut out = Vec::with_capacity(chains.len()); + for group in chains.chunks(crate::axi::SEQ_MAX) { + // the chain words by DMA, the sequencer, then every result word checked + let _ = writeln!(self.text, "D {}", group.len() * 6); + for (k, c) in group.iter().enumerate() { + for (i, &w) in c.iter().enumerate() { + let _ = writeln!(self.text, "{:04x} {w:08x}", CHAIN_RAM + (8 * k + i) as u16); + } + } + let _ = writeln!(self.text, "Q {}", group.len()); + let res = self.model.batch(group, t)?; + for (k, r) in res.iter().enumerate() { + for (i, &w) in r.iter().enumerate() { + self.expect(RESULT_RAM + (8 * k + i) as u16, w, 0xFFFF_FFFF); + } + } + self.expect(WRAP_DROPS, 0, 0xFFFF_FFFF); + out.extend(res); + } + Ok(out) + } } diff --git a/wai/wai-quantum-landscape-fpga/tb/tb_landscape_axi.v b/wai/wai-quantum-landscape-fpga/tb/tb_landscape_axi.v new file mode 100644 index 000000000..bdaa2115d --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/tb/tb_landscape_axi.v @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: Apache-2.0 +// Replays a session recorded from the host's software model in its AXI form (`gen-tb --axi`) +// against the AXI link (rtl/landscape_axi.v) and the engine behind it: register accesses over +// AXI-Lite, write lists through the DMA from a memory model that answers with irregular delays and +// several bursts outstanding, and batches of chains through the sequencer, whose results are read +// from the result RAM. Every read must return what the model returned. A mismatch names the line. +`timescale 1ns/1ps +module tb; + reg clk = 0; + always #5 clk = ~clk; // pl_clk0, 100 MHz + reg rstn = 0; + + // AXI-Lite master + reg [31:0] awaddr = 0, wdata = 0, araddr = 0; + reg awvalid = 0, wvalid = 0, bready = 0, arvalid = 0, rready = 0; + wire awready, wready, bvalid, arready, rvalid; + wire [1:0] bresp, rresp; + wire [31:0] rdata; + // DMA read port + wire [48:0] m_araddr; + wire [7:0] m_arlen; + wire [2:0] m_arsize, m_arprot; + wire [1:0] m_arburst; + wire [3:0] m_arcache; + wire m_arvalid, m_rready; + reg m_arready = 0, m_rvalid = 0, m_rlast = 0; + reg [63:0] m_rdata = 0; + reg [1:0] m_rresp = 0; + wire [94:0] emio_i; + + landscape_axi dut ( + .clk(clk), .rstn(rstn), + .s_awaddr(awaddr), .s_awvalid(awvalid), .s_awready(awready), .s_wdata(wdata), .s_wvalid(wvalid), + .s_wready(wready), .s_bresp(bresp), .s_bvalid(bvalid), .s_bready(bready), + .s_araddr(araddr), .s_arvalid(arvalid), .s_arready(arready), .s_rdata(rdata), .s_rresp(rresp), + .s_rvalid(rvalid), .s_rready(rready), + .m_araddr(m_araddr), .m_arlen(m_arlen), .m_arsize(m_arsize), .m_arburst(m_arburst), .m_arcache(m_arcache), + .m_arprot(m_arprot), .m_arvalid(m_arvalid), .m_arready(m_arready), .m_rdata(m_rdata), .m_rresp(m_rresp), + .m_rlast(m_rlast), .m_rvalid(m_rvalid), .m_rready(m_rready), + .emio_i(emio_i) + ); + + // ---- DDR: a memory model behind the high-performance port -------------------------------------- + localparam [48:0] BASE = 49'h0_1000_0000; + reg [63:0] mem [0:65535]; + reg [48:0] q_addr [0:15]; + reg [7:0] q_len [0:15]; + reg [4:0] q_head = 0, q_tail = 0; + reg [8:0] beat = 0; + integer seed = 7; + wire [4:0] q_used = q_tail - q_head; + always @(posedge clk) begin + // address channel: accept when there is room, sometimes late + m_arready <= 1'b0; + if (m_arvalid && !m_arready && q_used < 5'd8 && ($random(seed) & 3) != 0) begin + m_arready <= 1'b1; + q_addr[q_tail[3:0]] <= m_araddr; + q_len[q_tail[3:0]] <= m_arlen; + q_tail <= q_tail + 5'd1; + if (m_arsize != 3'd3 || m_arburst != 2'b01) begin $display("DMA: bad size or burst"); $finish; end + if ((m_araddr[11:0] + ({4'd0, m_arlen} + 12'd1) * 12'd8) > 13'h1000 && m_araddr[11:0] != 12'd0) begin + $display("DMA: a burst crosses a 4 KiB page at %h", m_araddr); $finish; + end + end + // data channel: one beat at a time, with gaps + if (m_rvalid && m_rready) begin + m_rvalid <= 1'b0; + if (m_rlast) begin q_head <= q_head + 5'd1; beat <= 0; end + else beat <= beat + 9'd1; + end else if (!m_rvalid && q_used != 5'd0 && ($random(seed) % 5) != 0) begin + m_rvalid <= 1'b1; + m_rdata <= mem[(q_addr[q_head[3:0]] - BASE) / 8 + beat]; + m_rlast <= (beat == {1'b0, q_len[q_head[3:0]]}); + m_rresp <= 2'b00; + end + end + + // ---- AXI-Lite master tasks -------------------------------------------------------------------- + // The master drives just after a clock edge and samples the slave's outputs at the edge, as + // they were before it: a handshake completes on the edge where valid and ready were both high. + integer line, t; + reg a_ok, w_ok, done_hs; + task axi_write(input [15:0] word, input [31:0] d); + begin + @(posedge clk); #1; + awaddr = 32'h8000_0000 | ({16'd0, word} << 2); wdata = d; awvalid = 1; wvalid = 1; + t = 0; + while (awvalid || wvalid) begin + @(posedge clk); + a_ok = awready; w_ok = wready; + #1; + if (a_ok) awvalid = 0; + if (w_ok) wvalid = 0; + t = t + 1; + if (t > 1000) begin $display("TIMEOUT write %h at line %0d", word, line); $finish; end + end + bready = 1; + done_hs = 0; t = 0; + while (!done_hs) begin + @(posedge clk); + done_hs = bvalid; + #1; + t = t + 1; + if (t > 1000) begin $display("TIMEOUT b at line %0d", line); $finish; end + end + bready = 0; + end + endtask + task axi_read(input [15:0] word, output [31:0] d); + begin + @(posedge clk); #1; + araddr = 32'h8000_0000 | ({16'd0, word} << 2); arvalid = 1; + t = 0; + while (arvalid) begin + @(posedge clk); + a_ok = arready; + #1; + if (a_ok) arvalid = 0; + t = t + 1; + if (t > 1000) begin $display("TIMEOUT ar %h at line %0d", word, line); $finish; end + end + rready = 1; + done_hs = 0; t = 0; + while (!done_hs) begin + @(posedge clk); + done_hs = rvalid; + d = rdata; + #1; + t = t + 1; + if (t > 1000) begin $display("TIMEOUT r at line %0d", line); $finish; end + end + rready = 0; + end + endtask + + integer fd, r, errors, reads, k, n, polls, longest; + reg [31:0] op, a, d, m, got; + reg [63:0] ent_a, ent_d; + + initial begin + errors = 0; reads = 0; line = 0; longest = 0; + repeat (20) @(posedge clk); + rstn = 1; + repeat (5) @(posedge clk); + if (emio_i[7:0] !== 8'hA5) begin $display("no signature"); $finish; end + axi_read(16'h8000, got); + if (got !== 32'h4C415849) begin $display("wrapper ID %h", got); $finish; end + fd = $fopen("landscape_axi_tb.script", "r"); + if (fd == 0) begin $display("no script"); $finish; end + r = $fscanf(fd, "%s", op); + while (r == 1) begin + line = line + 1; + if (op == "W") begin + r = $fscanf(fd, " %h %h", a, d); + axi_write(a[15:0], d); + end else if (op == "R") begin + r = $fscanf(fd, " %h %h %h", a, d, m); + axi_read(a[15:0], got); + reads = reads + 1; + if ((got & m) !== (d & m)) begin + errors = errors + 1; + if (errors <= 25) $display("MISMATCH line %0d: R %h got %h want %h", line, a[15:0], got, d); + end + end else if (op == "I") begin + got = 1; polls = 0; + while (got[0]) begin + axi_read(16'h0002, got); + polls = polls + 1; + if (polls > 200000) begin $display("TIMEOUT busy at line %0d", line); $finish; end + end + end else if (op == "D") begin + r = $fscanf(fd, " %d", n); + for (k = 0; k < n; k = k + 1) begin + r = $fscanf(fd, " %h %h", ent_a, ent_d); + mem[k] = {16'd0, ent_a[15:0], ent_d[31:0]}; + line = line + 1; + end + axi_write(16'h8002, BASE[31:0]); + axi_write(16'h8003, {15'd0, BASE[48:32]}); + axi_write(16'h8004, n); + axi_write(16'h8005, 1); + got = 2; polls = 0; + while (got[1]) begin + axi_read(16'h8001, got); + polls = polls + 1; + if (polls > 200000) begin $display("TIMEOUT DMA at line %0d", line); $finish; end + end + if (got[2]) begin $display("DMA error at line %0d", line); errors = errors + 1; end + end else if (op == "Q") begin + r = $fscanf(fd, " %d", n); + axi_write(16'h8010, n); + axi_write(16'h8011, 1); + // an engine access while the sequencer holds the engine: answered at once, dropped + axi_read(16'h0000, got); + if (got !== 32'hDEADBEEF) begin $display("an engine read during a batch returned %h at line %0d", got, line); errors = errors + 1; end + axi_read(16'h8008, got); + if (got !== 32'd1) begin $display("DROPS %0d after one dropped read, line %0d", got, line); errors = errors + 1; end + axi_write(16'h8008, 0); + got = 1; polls = 0; + while (got[0]) begin + axi_read(16'h8001, got); + polls = polls + 1; + if (polls > 2000000) begin $display("TIMEOUT sequencer at line %0d", line); $finish; end + end + if (polls > longest) longest = polls; + end else begin + $display("bad op at line %0d", line); + $finish; + end + r = $fscanf(fd, "%s", op); + end + $display("TB DONE: %0d lines, %0d checked reads, %0d mismatches, sim time %0t", line, reads, errors, $time); + $finish; + end +endmodule -- 2.54.0 (Apple Git-157) From bd86f60032132caeb966dab43e7d5819b7295380 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Fri, 6 Nov 2026 08:37:30 -0500 Subject: [PATCH 05/11] wai-quantum-landscape-fpga: read the processor's port widths before the first AXI access The AXI build assumes the low-power-domain master at 32 bits and the high-performance port reading 64 bits a beat, as the board's boot sets them; a different width would put data on the wrong byte lanes. The host reads both (ps-ports), touches no AXI address if the master's width differs, and falls back to plain writes if the port's does. The sampled cells of the conformance check vary in cost and temperature. Co-Authored-By: Claude Opus 5.5 --- wai/wai-quantum-landscape-fpga/PROVENANCE.md | 6 +++++ wai/wai-quantum-landscape-fpga/src/axi.rs | 28 +++++++++++++++++++- wai/wai-quantum-landscape-fpga/src/main.rs | 5 ++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/wai/wai-quantum-landscape-fpga/PROVENANCE.md b/wai/wai-quantum-landscape-fpga/PROVENANCE.md index b567336b9..25502958e 100644 --- a/wai/wai-quantum-landscape-fpga/PROVENANCE.md +++ b/wai/wai-quantum-landscape-fpga/PROVENANCE.md @@ -21,6 +21,12 @@ repository's `Apache-2.0`, by the copyright holder's decision of 2026-11-05. Thi own. Here the host is a `Member` of `wai_quantum::quantum_landscape`, so a job is the spec's JSON request, its answer the spec's answer, sealed, and the starts, default temperature, limits and refusals are the reference's, because they are the reference's code. +- **The link.** The first version had only the EMIO port. The AXI link (`rtl/landscape_axi.v`, + `src/axi.rs`), its testbench, the joint-statistics test (`src/exact.rs`) and the latency + measurement (`src/latency.rs`) were written here, after the release. +- **The member's contract.** The host certifies the member and gives it a schedule of temperatures, + as the spec's member trait now says; the first host stated its own parity and took a start + temperature and a sweep count. - **Names.** Modules, files and the engine's ID (`LAND`) are named for what they do. ## What is not here diff --git a/wai/wai-quantum-landscape-fpga/src/axi.rs b/wai/wai-quantum-landscape-fpga/src/axi.rs index afd4631f0..d526e39b0 100644 --- a/wai/wai-quantum-landscape-fpga/src/axi.rs +++ b/wai/wai-quantum-landscape-fpga/src/axi.rs @@ -161,6 +161,25 @@ mod board_port { Mem::map(GPIO_BASE, 0x1000).map(|g| g.rd(DATA_3_RO) & 0xFF == SIGNATURE).unwrap_or(false) } + /// The processing system's settings for the two ports the link uses, as the board's boot left + /// them (read only): the low-power-domain master's data width (LPD_SLCR AFI_FS, bits 9:8; 0 is + /// 32 bits, which the link's AXI-Lite port is built for) and the high-performance port's read + /// and write widths (AFIFM2 RDCTRL and WRCTRL, bits 1:0; 1 is 64 bits, which the DMA is built + /// for). A width other than the build's would put data on the wrong byte lanes. + pub fn ps_ports() -> Result<(u32, u32, u32, String), Error> { + let lpd = Mem::map(0xFF41_9000, 0x1000)?.rd(0); + let afi = Mem::map(0xFD38_0000, 0x1000)?; + let (rd, wr) = (afi.rd(0x000), afi.rd(0x014)); + let w = |v: u32, map: [&'static str; 4]| map[(v & 3) as usize]; + let text = format!( + "LPD_SLCR AFI_FS {lpd:#010x}: M_AXI_HPM0_LPD {} bits (the link wants 32); AFIFM2 RDCTRL {rd:#010x} WRCTRL {wr:#010x}: S_AXI_HP0_FPD reads {} bits, writes {} bits (the DMA wants 64 for reads)", + w(lpd >> 8, ["32", "64", "128", "?"]), + w(rd, ["128", "64", "32", "?"]), + w(wr, ["128", "64", "32", "?"]) + ); + Ok((lpd, rd, wr, text)) + } + pub struct AxiPort { lite: Mem, gpio: Mem, @@ -187,6 +206,10 @@ mod board_port { so no AXI address is touched. Load the AXI build first; if it was loaded, another session has replaced it." )); } + let (lpd, rd, _, text) = ps_ports()?; + if (lpd >> 8) & 3 != 0 { + return err(format!("the processing system's ports are not set as the AXI build needs, so no AXI address is touched: {text}")); + } let lite = Mem::map(LITE_BASE, LITE_LEN)?; let mut p = AxiPort { lite, gpio, guard: true, dma: None, dma_min: 32, sequencer: true, accesses: 0, dma_lists: 0, batches: 0 }; let id = p.rd(WRAP_ID)?; @@ -202,7 +225,10 @@ mod board_port { return err(format!("the AXI link is busy at open (status {st:#x}): another process is using it")); } p.wr(WRAP_DROPS, 0)?; - p.dma = Some(Dma::new(16)?); + // the DMA only where the port reads 64 bits a beat, as the link is built for + if rd & 3 == 1 { + p.dma = Some(Dma::new(16)?); + } Ok(p) } diff --git a/wai/wai-quantum-landscape-fpga/src/main.rs b/wai/wai-quantum-landscape-fpga/src/main.rs index 080c57de0..52a8d5f1e 100644 --- a/wai/wai-quantum-landscape-fpga/src/main.rs +++ b/wai/wai-quantum-landscape-fpga/src/main.rs @@ -330,6 +330,11 @@ fn main() { println!("the engine answers with its ID and version, over its {link} link"); } #[cfg(unix)] + "ps-ports" => { + let (_, _, _, text) = wai_quantum_landscape_fpga::axi::ps_ports().unwrap_or_else(|e| die(e)); + println!("{text}"); + } + #[cfg(unix)] "release" => { wai_quantum_landscape_fpga::emio::EmioPort::release_to_boot_state().unwrap_or_else(|e| die(e)); println!("EMIO banks 3-5: inputs, outputs disabled, data 0"); -- 2.54.0 (Apple Git-157) From ee85a21415c7acea6471be8bae20b91286135e98 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Fri, 6 Nov 2026 10:03:06 -0500 Subject: [PATCH 06/11] wai-quantum-landscape-fpga: pre-register a repeat of the null that failed Co-Authored-By: Claude Opus 5.5 --- .../measurements/2026-11-06/NULL-REPEAT.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/NULL-REPEAT.txt diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/NULL-REPEAT.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/NULL-REPEAT.txt new file mode 100644 index 000000000..c63c0fd9a --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/NULL-REPEAT.txt @@ -0,0 +1,15 @@ +Written 2026-11-06 after the class campaign's data were read, and committed before the repeat +below runs. Not edited after its data. + +The class campaign's null (EMIO build, 12:49 to 13:13 UTC) came to 8.1 +/- 1.9 mW, and so failed its +pre-registered rule, |delta| < 2 SE. The two slots of a null are the same state (halted), so no job +explains it, and it does not follow the order of the slots: 3 of the 8 passes put the slot labelled +halt first and 5 the slot labelled run, and the differences are alike in both. + +WHAT IS DONE. The null runs once more, on the AXI build, by the same protocol: 8 passes, 60 s settle, +30 samples, nothing else on the board, the same rule. Both nulls are reported, whichever way the +second comes out. This is not a second chance for the first: its failure stands in the README. + +WHAT FOLLOWS. If the repeat also fails, nothing below about 10 mW is claimed above the instrument's +floor, which covers gibbs-recall and t0-idle. If it passes, those two are still reported with the +first null beside them, since one failure in two says the floor is not known to be under 8 mW. -- 2.54.0 (Apple Git-157) From f6cfa507ae9f7071a43a381e3b9f5f1f47b3ced4 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Fri, 6 Nov 2026 10:29:59 -0500 Subject: [PATCH 07/11] wai-quantum-landscape-fpga: pre-register the final AXI build's power check Co-Authored-By: Claude Opus 5.5 --- .../measurements/2026-11-06/AXI-FINAL-PREDICTION.txt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/AXI-FINAL-PREDICTION.txt diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/AXI-FINAL-PREDICTION.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/AXI-FINAL-PREDICTION.txt new file mode 100644 index 000000000..097afead7 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/AXI-FINAL-PREDICTION.txt @@ -0,0 +1,9 @@ +Written 2026-11-06, before the build with the result write-back was metered, and committed before +the measurement. Not edited after the data. + +The AXI build metered first (sha256 3d84dbd3..., AXI-PREDICTION.txt) read a batch's results by +register reads. The final build adds a write master that sends them to DDR. The engine is the same, +and the write master is idle while the meter reads. + +PREDICTION: t0-recall run minus halt on the final build, 3 passes, 60 s settle, 30 samples, equals +the EMIO build's class result, 162.0 +/- 3.3 mW, within 2 combined standard errors. -- 2.54.0 (Apple Git-157) From c2db9d17d127d9ba1de85149f572592a27f599da Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Fri, 6 Nov 2026 10:30:14 -0500 Subject: [PATCH 08/11] wai-quantum-landscape-fpga: the link meets the board as it boots, and results go back by DMA The processor's master and high-performance ports boot at 128 bits and loading a bitstream does not change them, so the AXI build meets them with width converters and writes no processor register; ps-ports reads them, and the host touches no AXI address if they differ. The registers move to M_AXI_HPM0_FPD at 0xA000_0000, a port designs on this board have already used. A write master sends a batch's results to DDR, a page at a time, and the testbench checks every word it writes. Writes are now timed to their response. The class campaign's logs are added. Co-Authored-By: Claude Opus 5.5 --- .../2026-11-06-landscape-fpga-member.md | 5 +- wai/wai-quantum-landscape-fpga/README.md | 149 +++-- wai/wai-quantum-landscape-fpga/landscape.tcl | 34 +- .../2026-11-06/emio-classes/gibbs-recall.dat | 629 ++++++++++++++++++ .../2026-11-06/emio-classes/null.dat | 505 ++++++++++++++ .../2026-11-06/emio-classes/step.dat | 610 +++++++++++++++++ .../2026-11-06/emio-classes/t0-idle.dat | 505 ++++++++++++++ .../emio-classes/t0-recall-50MHz.dat | 381 +++++++++++ .../2026-11-06/emio-classes/t0-recall.dat | 629 ++++++++++++++++++ .../2026-11-06/first-attempt/known_answer.dat | 483 ++++++++++++++ .../2026-11-06/first-attempt/step.dat | 610 +++++++++++++++++ .../rtl/landscape_axi.v | 109 ++- .../rtl/landscape_top_axi.v | 29 +- wai/wai-quantum-landscape-fpga/src/axi.rs | 145 +++- wai/wai-quantum-landscape-fpga/src/latency.rs | 35 +- wai/wai-quantum-landscape-fpga/src/main.rs | 6 +- wai/wai-quantum-landscape-fpga/src/script.rs | 11 +- .../tb/tb_landscape_axi.v | 81 ++- 18 files changed, 4844 insertions(+), 112 deletions(-) create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/gibbs-recall.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/null.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/step.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/t0-idle.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/t0-recall-50MHz.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/t0-recall.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/first-attempt/known_answer.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/first-attempt/step.dat diff --git a/wai/changes/2026-11-06-landscape-fpga-member.md b/wai/changes/2026-11-06-landscape-fpga-member.md index 1927e3165..3f72611f7 100644 --- a/wai/changes/2026-11-06-landscape-fpga-member.md +++ b/wai/changes/2026-11-06-landscape-fpga-member.md @@ -1,6 +1,9 @@ ### wai-quantum-landscape-fpga: an FPGA board as a member of energy-landscape jobs - **`wai-quantum-landscape-fpga`** is a reference design: the settle and sample kernels of `wai.quantum.landscape/1` in programmable logic for up to 64 cells (`rtl/`), the register-level model the logic is tested against, a testbench that replays the model's session against the logic, and the host that runs jobs on the board as a `Member`, holds it to the conformance set and meters its power by run minus halt. The board, its part and its sensor are named as data in its README. -- **The sample kernel takes the reference's draws**: the logic counts and mixes them as SplitMix64 does and compares 53 bits against cut points the host computes with the reference's `cuts`, so it needs no exponential. On the board both kernels are bit-exact against the `settle` and `chains` vectors, every worked example over 200 seeds answers as the CPU reference does, and the member earns its parity by running those vectors when it opens. +- **The sample kernel takes the reference's draws**: the logic counts and mixes them as SplitMix64 does and compares 53 bits against cut points the host computes with the reference's `cuts`, so it needs no exponential. On the board both kernels are bit-exact against the `settle` and `chains` vectors, and every worked example over 200 seeds answers as the CPU reference does. +- **An AXI link** (`rtl/landscape_axi.v`) replaces the EMIO register port as the default build: the engine's registers on AXI-Lite, bulk loads read from DDR by a write-list DMA, a sequencer that runs a job's chains in fabric, start to result, and a write-back that puts the results in DDR. The link answers every access within a few clocks whatever the engine is doing, and the host reads a signature over EMIO before every AXI access, so a design replaced by another session is caught before an access could go unanswered. A second testbench drives the link and its DMA against the model. The processor-to-logic latency is measured on the board (`latency`). +- **The sample kernel is held to its exact joint law** (`joint`, `src/exact.rs`): small workloads enumerated over every state, chains on the board against that law by chi-square and G over all states, every cell's and every pair's frequencies, and a control with the right marginals and independent cells that must be rejected. +- **The member is certified by the host** on the conformance set and the statistical battery, runs any schedule of temperatures the job gives, and maps fields and costs beyond its registers exactly for sweeps at 0. - **The measurements** are board-level, one board and one meter, each campaign with its prediction committed first (`measurements/`). No bitstream is distributed: the vendor tool's licence limits a bitstream to that vendor's devices, so the README gives the build steps. - The design came from a private repository and is released under `Apache-2.0` by its copyright holder (`wai-quantum-landscape-fpga/PROVENANCE.md`). diff --git a/wai/wai-quantum-landscape-fpga/README.md b/wai/wai-quantum-landscape-fpga/README.md index 5e2fdbf4d..74bbcc137 100644 --- a/wai/wai-quantum-landscape-fpga/README.md +++ b/wai/wai-quantum-landscape-fpga/README.md @@ -18,11 +18,13 @@ counter-based generator, and nothing here is a quantum effect. Every answer says | 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`), whole board, 10 mW steps | | clock | the processing system's `pl_clk0`, 100 MHz | -| job port | 95 EMIO lines of the processing system's GPIO controller, through `/dev/mem` | +| 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`. Nothing else is board-specific except `landscape.tcl` (part and -board part), `src/emio.rs` (the GPIO controller's addresses) and `src/board.rs` (the FPGA manager, -the clock register, the sensor's name). +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 @@ -50,23 +52,62 @@ cell returns −1 if `u < a`, 0 if `u < b`, else +1. The table holds 8,192 pairs 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 continues the chain, the counter running on. +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 does not assume its parity: on opening, it runs the conformance -set's `settle` and `chains` vectors on the device and states `bit-exact` for a kernel only if every -vector comes back bit for bit. The reader, the truth, the energy, the verdict, 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. +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 comes back bit for bit (and, for a sampling +kernel that misses, trying the statistical battery). 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. -**The job port is EMIO, not AXI.** A processor load from an AXI address whose logic is gone never -completes and hangs the board until it is power-cycled; on a shared board another session's reload -can do that between two accesses. An EMIO line is a processor register: with the design gone a read -returns junk, the ID check fails, and the job gets an error. The cost is about 1.6 µs a register -access. +**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, +holds it to that). Sampling with such fields is refused. -**Limits, and what the host refuses with a sentence:** more than 64 variables; a field beyond -±524,287 or a cost beyond 1,048,575; a job whose uncertain fields span more than 8,192. +**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 pinned +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 cleans and invalidates the page before the write-back and invalidates +it after, because the port is not coherent. An error is reported, and the write-back's done count +proves it ran. + +**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. + +**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 @@ -76,23 +117,23 @@ access. | 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, and we added swap while it built | -| a Rust toolchain | the repository's pinned version (`rust-toolchain.toml`), with the `aarch64-unknown-linux-gnu` target and `aarch64-linux-gnu-gcc` | +| 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 one vendor block is the processing system's configuration -(`zynq_ultra_ps_e` 3.5), and the build checks out only the tool's own synthesis and implementation -features. +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 -sh wai-quantum-landscape-fpga/scripts/build_bitstream.sh # -> landscape.bit.bin, and its sha256 +# 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 +./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 @@ -102,17 +143,19 @@ bitstream load needs it set to 0 first; `scripts/run_all.sh` does that and puts §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 bitstream measured below had sha256 -`feea5015b2f8d85049fd48bcc66493a411d551326365f67b1db2ae65ec00074c`; whether a rebuild gives the same -bytes has not been checked. +instead. The bitstreams measured below had sha256 `9617a1da32c1fbd6eba19d4abb2f5a11f9122a3c3c41abde48e316e4f703f62a` (the AXI build) 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, random settles, field read-back +./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 +./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 - ``` @@ -122,14 +165,21 @@ echo '{"program": "vars 2\noppose 0 1\nsolve\n", "seed": "7"}' | ./wai-quantum-l 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 -- gen-tb s.txt # the testbench's script, recorded from the model +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 -- gen-tb s.txt [--axi] # a testbench's script, recorded from the model ``` -The testbench (`tb/tb_landscape.v`, plain Verilog) replays that script through the EMIO port and -its synchroniser and compares every read with the model's. We ran it in the vendor's simulator -(xsim 2026.1): 11,963 lines, 285 checked reads, 0 mismatches, and a flipped expectation is reported. -An open-source simulator was not tried. +The testbenches (plain Verilog) replay that script 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. In the vendor's simulator (xsim 2026.1): the EMIO script, +40,458 lines and 530 checked reads, and the AXI script, 40,800 lines, 76 write lists, 22 batches and +852 checked values, both with 0 mismatches; a flipped expectation is reported. An open-source +simulator was not tried. `scripts/run_all.sh` does all of it on the board in order, then the power campaign. @@ -176,22 +226,33 @@ with its couplings fixed in the logic. 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, 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), and does not schedule different chains on different cores. -- **The job port is slow.** At 1.6 µs a register access, each table entry is four writes; a job with - long schedules over wide windows spends most of its time writing the table, not computing. +- **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, about a + microsecond later; a reload by another session landing inside that window would still hang the + board. It has not been seen; it is not excluded. +- **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. +- **Sampling is limited to the engine's registers**: fields of ±524,287 and a cost of 1,048,575. + Beyond them it refuses; at temperature 0 they are mapped exactly. +- **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. ## Files | | | |---|---| | `rtl/landscape_core.v` | the engine: cones, the T0, GIBBS and JACOBI modes, the draw pipeline, the table, counters | -| `rtl/landscape_port.v`, `rtl/landscape_top.v` | the EMIO port and the top level | -| `rtl/landscape_multi.v`, `rtl/landscape_top_multi.v` | several engines behind one port | -| `landscape.tcl`, `rtl/landscape.xdc` | the build, and the constraints on the port's crossing | -| `src/` | the host: register map, ports, driver, member, model, checks, power meter, command line | -| `tb/tb_landscape.v` | the testbench | +| `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/` | build, run-everything, power campaign, and the two analysis scripts | | `measurements/` | each campaign's prediction and raw logs | diff --git a/wai/wai-quantum-landscape-fpga/landscape.tcl b/wai/wai-quantum-landscape-fpga/landscape.tcl index 0eb347637..13fd4fce8 100644 --- a/wai/wai-quantum-landscape-fpga/landscape.tcl +++ b/wai/wai-quantum-landscape-fpga/landscape.tcl @@ -41,30 +41,44 @@ connect_bd_net [get_bd_pins ps/emio_gpio_i] [get_bd_ports emio_i] create_bd_port -dir O -type clk pl_clk0 connect_bd_net [get_bd_pins ps/pl_clk0] [get_bd_ports pl_clk0] if {$link eq "axi"} { - # M_AXI_HPM0_LPD (32-bit) -> protocol converter -> LITE at 0x8000_0000, 256 KiB. - # DMA (AXI4, 64-bit, read only) -> S_AXI_HP0_FPD -> DDR. The reset is the fabric's own (aresetn). - set_property -dict [list CONFIG.PSU__USE__M_AXI_GP2 {1} CONFIG.PSU__MAXIGP2__DATA_WIDTH {32} \ - CONFIG.PSU__USE__S_AXI_GP2 {1} CONFIG.PSU__SAXIGP2__DATA_WIDTH {64}] [get_bd_cells ps] - connect_bd_net [get_bd_pins ps/pl_clk0] [get_bd_pins ps/maxihpm0_lpd_aclk] + # The two port widths are the ones this board's boot leaves set (src/axi.rs, ps_ports): loading + # a bitstream through the FPGA manager does not change them, so the design meets them as they + # are and writes no processor register. M_AXI_HPM0_FPD (128-bit) -> width converter -> protocol + # converter -> LITE (32-bit) at 0xA000_0000, 256 KiB. DMA (AXI4, 64-bit) -> width converter -> + # S_AXI_HP0_FPD (128-bit) -> DDR. The reset is the fabric's own (aresetn). + set_property -dict [list CONFIG.PSU__USE__M_AXI_GP0 {1} CONFIG.PSU__MAXIGP0__DATA_WIDTH {128} \ + CONFIG.PSU__USE__S_AXI_GP2 {1} CONFIG.PSU__SAXIGP2__DATA_WIDTH {128}] [get_bd_cells ps] + connect_bd_net [get_bd_pins ps/pl_clk0] [get_bd_pins ps/maxihpm0_fpd_aclk] connect_bd_net [get_bd_pins ps/pl_clk0] [get_bd_pins ps/saxihp0_fpd_aclk] create_bd_port -dir I -type rst aresetn + set dw [lindex [get_ipdefs *axi_dwidth_converter*] end] + create_bd_cell -type ip -vlnv $dw down + create_bd_cell -type ip -vlnv $dw up + set_property -dict [list CONFIG.SI_DATA_WIDTH {128} CONFIG.MI_DATA_WIDTH {32}] [get_bd_cells down] + set_property -dict [list CONFIG.SI_DATA_WIDTH {64} CONFIG.MI_DATA_WIDTH {128}] [get_bd_cells up] create_bd_cell -type ip -vlnv [lindex [get_ipdefs *axi_protocol_converter*] end] conv set_property -dict [list CONFIG.SI_PROTOCOL {AXI4} CONFIG.MI_PROTOCOL {AXI4LITE} CONFIG.DATA_WIDTH {32}] [get_bd_cells conv] + foreach c {down up} { + connect_bd_net [get_bd_pins ps/pl_clk0] [get_bd_pins $c/s_axi_aclk] + connect_bd_net [get_bd_ports aresetn] [get_bd_pins $c/s_axi_aresetn] + } connect_bd_net [get_bd_pins ps/pl_clk0] [get_bd_pins conv/aclk] connect_bd_net [get_bd_ports aresetn] [get_bd_pins conv/aresetn] - connect_bd_intf_net [get_bd_intf_pins ps/M_AXI_HPM0_LPD] [get_bd_intf_pins conv/S_AXI] + connect_bd_intf_net [get_bd_intf_pins ps/M_AXI_HPM0_FPD] [get_bd_intf_pins down/S_AXI] + connect_bd_intf_net [get_bd_intf_pins down/M_AXI] [get_bd_intf_pins conv/S_AXI] create_bd_intf_port -mode Master -vlnv xilinx.com:interface:aximm_rtl:1.0 LITE set_property -dict [list CONFIG.PROTOCOL {AXI4LITE} CONFIG.ADDR_WIDTH {32} CONFIG.DATA_WIDTH {32}] [get_bd_intf_ports LITE] connect_bd_intf_net [get_bd_intf_pins conv/M_AXI] [get_bd_intf_ports LITE] create_bd_intf_port -mode Slave -vlnv xilinx.com:interface:aximm_rtl:1.0 DMA - set_property -dict [list CONFIG.PROTOCOL {AXI4} CONFIG.ADDR_WIDTH {49} CONFIG.DATA_WIDTH {64} CONFIG.ID_WIDTH {6} \ - CONFIG.READ_WRITE_MODE {READ_ONLY} CONFIG.HAS_REGION {0} CONFIG.HAS_QOS {0} CONFIG.HAS_LOCK {0}] [get_bd_intf_ports DMA] - connect_bd_intf_net [get_bd_intf_ports DMA] [get_bd_intf_pins ps/S_AXI_HP0_FPD] + set_property -dict [list CONFIG.PROTOCOL {AXI4} CONFIG.ADDR_WIDTH {49} CONFIG.DATA_WIDTH {64} CONFIG.ID_WIDTH {0} \ + CONFIG.HAS_REGION {0} CONFIG.HAS_QOS {0} CONFIG.HAS_LOCK {0}] [get_bd_intf_ports DMA] + connect_bd_intf_net [get_bd_intf_ports DMA] [get_bd_intf_pins up/S_AXI] + connect_bd_intf_net [get_bd_intf_pins up/M_AXI] [get_bd_intf_pins ps/S_AXI_HP0_FPD] set_property CONFIG.ASSOCIATED_BUSIF {LITE:DMA} [get_bd_ports pl_clk0] set_property CONFIG.ASSOCIATED_RESET {aresetn} [get_bd_ports pl_clk0] assign_bd_address set lite [get_bd_addr_segs -of_objects [get_bd_addr_spaces ps/Data] -filter {NAME =~ *LITE*}] - set_property offset 0x80000000 $lite + set_property offset 0xA0000000 $lite set_property range 256K $lite foreach seg [get_bd_addr_segs -of_objects [get_bd_addr_spaces ps/Data]] { puts "ADDR $seg [get_property offset $seg] [get_property range $seg]" } } diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/gibbs-recall.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/gibbs-recall.dat new file mode 100644 index 000000000..3f84f135f --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/gibbs-recall.dat @@ -0,0 +1,629 @@ +# class gibbs-recall cores 1 +# wai-quantum-fpga meter passes=10 settle=60s samples=30 null=false sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793972579, tv_nsec: 29912947 } +# pass state sample power_uW +1 run 1 3150000 +1 run 2 3150000 +1 run 3 3140000 +1 run 4 3150000 +1 run 5 3150000 +1 run 6 3150000 +1 run 7 3150000 +1 run 8 3150000 +1 run 9 3160000 +1 run 10 3150000 +1 run 11 3150000 +1 run 12 3160000 +1 run 13 3150000 +1 run 14 3150000 +1 run 15 3150000 +1 run 16 3150000 +1 run 17 3160000 +1 run 18 3170000 +1 run 19 3150000 +1 run 20 3160000 +1 run 21 3150000 +1 run 22 3150000 +1 run 23 3150000 +1 run 24 3150000 +1 run 25 3140000 +1 run 26 3150000 +1 run 27 3150000 +1 run 28 3150000 +1 run 29 3150000 +1 run 30 3150000 +# point pass=1 state=run jobs_per_s=48542.7 updates_per_s=24853863.3 clock_hz=99997947 +1 halt 1 3140000 +1 halt 2 3140000 +1 halt 3 3130000 +1 halt 4 3130000 +1 halt 5 3140000 +1 halt 6 3160000 +1 halt 7 3130000 +1 halt 8 3120000 +1 halt 9 3120000 +1 halt 10 3130000 +1 halt 11 3140000 +1 halt 12 3140000 +1 halt 13 3140000 +1 halt 14 3140000 +1 halt 15 3150000 +1 halt 16 3140000 +1 halt 17 3130000 +1 halt 18 3140000 +1 halt 19 3130000 +1 halt 20 3130000 +1 halt 21 3140000 +1 halt 22 3140000 +1 halt 23 3150000 +1 halt 24 3130000 +1 halt 25 3140000 +1 halt 26 3140000 +1 halt 27 3120000 +1 halt 28 3140000 +1 halt 29 3130000 +1 halt 30 3130000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997947 +2 run 1 3130000 +2 run 2 3140000 +2 run 3 3160000 +2 run 4 3140000 +2 run 5 3140000 +2 run 6 3140000 +2 run 7 3140000 +2 run 8 3140000 +2 run 9 3140000 +2 run 10 3140000 +2 run 11 3150000 +2 run 12 3150000 +2 run 13 3140000 +2 run 14 3160000 +2 run 15 3140000 +2 run 16 3140000 +2 run 17 3140000 +2 run 18 3190000 +2 run 19 3150000 +2 run 20 3140000 +2 run 21 3140000 +2 run 22 3150000 +2 run 23 3140000 +2 run 24 3140000 +2 run 25 3140000 +2 run 26 3160000 +2 run 27 3140000 +2 run 28 3140000 +2 run 29 3140000 +2 run 30 3140000 +# point pass=2 state=run jobs_per_s=48542.7 updates_per_s=24853863.2 clock_hz=99997947 +2 halt 1 3120000 +2 halt 2 3120000 +2 halt 3 3130000 +2 halt 4 3130000 +2 halt 5 3130000 +2 halt 6 3140000 +2 halt 7 3130000 +2 halt 8 3140000 +2 halt 9 3130000 +2 halt 10 3130000 +2 halt 11 3140000 +2 halt 12 3130000 +2 halt 13 3130000 +2 halt 14 3120000 +2 halt 15 3140000 +2 halt 16 3140000 +2 halt 17 3130000 +2 halt 18 3140000 +2 halt 19 3140000 +2 halt 20 3140000 +2 halt 21 3130000 +2 halt 22 3130000 +2 halt 23 3140000 +2 halt 24 3140000 +2 halt 25 3120000 +2 halt 26 3140000 +2 halt 27 3130000 +2 halt 28 3120000 +2 halt 29 3120000 +2 halt 30 3130000 +# point pass=2 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997947 +3 run 1 3140000 +3 run 2 3130000 +3 run 3 3140000 +3 run 4 3140000 +3 run 5 3150000 +3 run 6 3140000 +3 run 7 3140000 +3 run 8 3140000 +3 run 9 3140000 +3 run 10 3140000 +3 run 11 3140000 +3 run 12 3150000 +3 run 13 3140000 +3 run 14 3140000 +3 run 15 3140000 +3 run 16 3140000 +3 run 17 3140000 +3 run 18 3140000 +3 run 19 3150000 +3 run 20 3160000 +3 run 21 3140000 +3 run 22 3140000 +3 run 23 3130000 +3 run 24 3140000 +3 run 25 3130000 +3 run 26 3140000 +3 run 27 3140000 +3 run 28 3150000 +3 run 29 3140000 +3 run 30 3130000 +# point pass=3 state=run jobs_per_s=48542.7 updates_per_s=24853863.4 clock_hz=99997947 +3 halt 1 3120000 +3 halt 2 3130000 +3 halt 3 3130000 +3 halt 4 3120000 +3 halt 5 3120000 +3 halt 6 3120000 +3 halt 7 3130000 +3 halt 8 3140000 +3 halt 9 3120000 +3 halt 10 3130000 +3 halt 11 3120000 +3 halt 12 3110000 +3 halt 13 3120000 +3 halt 14 3130000 +3 halt 15 3120000 +3 halt 16 3140000 +3 halt 17 3140000 +3 halt 18 3120000 +3 halt 19 3120000 +3 halt 20 3120000 +3 halt 21 3130000 +3 halt 22 3120000 +3 halt 23 3120000 +3 halt 24 3110000 +3 halt 25 3140000 +3 halt 26 3120000 +3 halt 27 3110000 +3 halt 28 3120000 +3 halt 29 3110000 +3 halt 30 3120000 +# point pass=3 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997947 +4 run 1 3140000 +4 run 2 3150000 +4 run 3 3140000 +4 run 4 3150000 +4 run 5 3150000 +4 run 6 3130000 +4 run 7 3130000 +4 run 8 3140000 +4 run 9 3140000 +4 run 10 3140000 +4 run 11 3150000 +4 run 12 3140000 +4 run 13 3160000 +4 run 14 3150000 +4 run 15 3140000 +4 run 16 3140000 +4 run 17 3140000 +4 run 18 3150000 +4 run 19 3150000 +4 run 20 3140000 +4 run 21 3150000 +4 run 22 3140000 +4 run 23 3140000 +4 run 24 3140000 +4 run 25 3140000 +4 run 26 3150000 +4 run 27 3140000 +4 run 28 3150000 +4 run 29 3150000 +4 run 30 3140000 +# point pass=4 state=run jobs_per_s=48542.7 updates_per_s=24853863.2 clock_hz=99997946 +4 halt 1 3120000 +4 halt 2 3160000 +4 halt 3 3140000 +4 halt 4 3120000 +4 halt 5 3110000 +4 halt 6 3130000 +4 halt 7 3140000 +4 halt 8 3130000 +4 halt 9 3110000 +4 halt 10 3150000 +4 halt 11 3120000 +4 halt 12 3140000 +4 halt 13 3130000 +4 halt 14 3130000 +4 halt 15 3130000 +4 halt 16 3130000 +4 halt 17 3140000 +4 halt 18 3130000 +4 halt 19 3130000 +4 halt 20 3130000 +4 halt 21 3140000 +4 halt 22 3130000 +4 halt 23 3130000 +4 halt 24 3130000 +4 halt 25 3140000 +4 halt 26 3130000 +4 halt 27 3130000 +4 halt 28 3130000 +4 halt 29 3120000 +4 halt 30 3130000 +# point pass=4 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997946 +5 run 1 3140000 +5 run 2 3140000 +5 run 3 3140000 +5 run 4 3140000 +5 run 5 3140000 +5 run 6 3140000 +5 run 7 3170000 +5 run 8 3130000 +5 run 9 3150000 +5 run 10 3150000 +5 run 11 3140000 +5 run 12 3150000 +5 run 13 3140000 +5 run 14 3140000 +5 run 15 3150000 +5 run 16 3140000 +5 run 17 3140000 +5 run 18 3140000 +5 run 19 3150000 +5 run 20 3150000 +5 run 21 3150000 +5 run 22 3160000 +5 run 23 3140000 +5 run 24 3160000 +5 run 25 3150000 +5 run 26 3140000 +5 run 27 3140000 +5 run 28 3150000 +5 run 29 3140000 +5 run 30 3160000 +# point pass=5 state=run jobs_per_s=48542.7 updates_per_s=24853863.1 clock_hz=99997946 +5 halt 1 3140000 +5 halt 2 3120000 +5 halt 3 3130000 +5 halt 4 3120000 +5 halt 5 3120000 +5 halt 6 3120000 +5 halt 7 3150000 +5 halt 8 3120000 +5 halt 9 3130000 +5 halt 10 3140000 +5 halt 11 3120000 +5 halt 12 3510000 +5 halt 13 3130000 +5 halt 14 3130000 +5 halt 15 3130000 +5 halt 16 3130000 +5 halt 17 3140000 +5 halt 18 3150000 +5 halt 19 3140000 +5 halt 20 3140000 +5 halt 21 3130000 +5 halt 22 3140000 +5 halt 23 3130000 +5 halt 24 3130000 +5 halt 25 3190000 +5 halt 26 3140000 +5 halt 27 3150000 +5 halt 28 3130000 +5 halt 29 3140000 +5 halt 30 3150000 +# point pass=5 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997946 +6 halt 1 3130000 +6 halt 2 3140000 +6 halt 3 3140000 +6 halt 4 3150000 +6 halt 5 3140000 +6 halt 6 3140000 +6 halt 7 3150000 +6 halt 8 3140000 +6 halt 9 3140000 +6 halt 10 3140000 +6 halt 11 3130000 +6 halt 12 3140000 +6 halt 13 3130000 +6 halt 14 3140000 +6 halt 15 3160000 +6 halt 16 3150000 +6 halt 17 3140000 +6 halt 18 3140000 +6 halt 19 3140000 +6 halt 20 3140000 +6 halt 21 3140000 +6 halt 22 3140000 +6 halt 23 3140000 +6 halt 24 3150000 +6 halt 25 3140000 +6 halt 26 3120000 +6 halt 27 3130000 +6 halt 28 3140000 +6 halt 29 3130000 +6 halt 30 3130000 +# point pass=6 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997946 +6 run 1 3140000 +6 run 2 3140000 +6 run 3 3150000 +6 run 4 3150000 +6 run 5 3140000 +6 run 6 3140000 +6 run 7 3150000 +6 run 8 3140000 +6 run 9 3140000 +6 run 10 3140000 +6 run 11 3140000 +6 run 12 3150000 +6 run 13 3140000 +6 run 14 3150000 +6 run 15 3140000 +6 run 16 3140000 +6 run 17 3150000 +6 run 18 3140000 +6 run 19 3140000 +6 run 20 3140000 +6 run 21 3140000 +6 run 22 3140000 +6 run 23 3150000 +6 run 24 3140000 +6 run 25 3140000 +6 run 26 3140000 +6 run 27 3150000 +6 run 28 3140000 +6 run 29 3130000 +6 run 30 3140000 +# point pass=6 state=run jobs_per_s=48542.7 updates_per_s=24853862.9 clock_hz=99997946 +7 halt 1 3150000 +7 halt 2 3120000 +7 halt 3 3130000 +7 halt 4 3130000 +7 halt 5 3140000 +7 halt 6 3130000 +7 halt 7 3130000 +7 halt 8 3140000 +7 halt 9 3150000 +7 halt 10 3130000 +7 halt 11 3130000 +7 halt 12 3140000 +7 halt 13 3120000 +7 halt 14 3130000 +7 halt 15 3130000 +7 halt 16 3130000 +7 halt 17 3130000 +7 halt 18 3140000 +7 halt 19 3130000 +7 halt 20 3120000 +7 halt 21 3140000 +7 halt 22 3120000 +7 halt 23 3130000 +7 halt 24 3140000 +7 halt 25 3140000 +7 halt 26 3130000 +7 halt 27 3130000 +7 halt 28 3140000 +7 halt 29 3130000 +7 halt 30 3120000 +# point pass=7 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997946 +7 run 1 3150000 +7 run 2 3150000 +7 run 3 3150000 +7 run 4 3140000 +7 run 5 3150000 +7 run 6 3140000 +7 run 7 3140000 +7 run 8 3140000 +7 run 9 3150000 +7 run 10 3150000 +7 run 11 3150000 +7 run 12 3160000 +7 run 13 3160000 +7 run 14 3150000 +7 run 15 3150000 +7 run 16 3150000 +7 run 17 3150000 +7 run 18 3150000 +7 run 19 3150000 +7 run 20 3160000 +7 run 21 3160000 +7 run 22 3150000 +7 run 23 3140000 +7 run 24 3150000 +7 run 25 3150000 +7 run 26 3140000 +7 run 27 3150000 +7 run 28 3150000 +7 run 29 3160000 +7 run 30 3140000 +# point pass=7 state=run jobs_per_s=48542.7 updates_per_s=24853862.9 clock_hz=99997946 +8 run 1 3140000 +8 run 2 3140000 +8 run 3 3140000 +8 run 4 3140000 +8 run 5 3140000 +8 run 6 3140000 +8 run 7 3150000 +8 run 8 3140000 +8 run 9 3130000 +8 run 10 3160000 +8 run 11 3140000 +8 run 12 3140000 +8 run 13 3140000 +8 run 14 3140000 +8 run 15 3140000 +8 run 16 3140000 +8 run 17 3140000 +8 run 18 3140000 +8 run 19 3140000 +8 run 20 3160000 +8 run 21 3150000 +8 run 22 3140000 +8 run 23 3140000 +8 run 24 3150000 +8 run 25 3140000 +8 run 26 3140000 +8 run 27 3150000 +8 run 28 3140000 +8 run 29 3140000 +8 run 30 3140000 +# point pass=8 state=run jobs_per_s=48542.7 updates_per_s=24853862.9 clock_hz=99997946 +8 halt 1 3130000 +8 halt 2 3140000 +8 halt 3 3140000 +8 halt 4 3140000 +8 halt 5 3130000 +8 halt 6 3130000 +8 halt 7 3130000 +8 halt 8 3140000 +8 halt 9 3150000 +8 halt 10 3130000 +8 halt 11 3120000 +8 halt 12 3130000 +8 halt 13 3130000 +8 halt 14 3130000 +8 halt 15 3140000 +8 halt 16 3140000 +8 halt 17 3140000 +8 halt 18 3150000 +8 halt 19 3140000 +8 halt 20 3120000 +8 halt 21 3120000 +8 halt 22 3130000 +8 halt 23 3130000 +8 halt 24 3140000 +8 halt 25 3120000 +8 halt 26 3130000 +8 halt 27 3120000 +8 halt 28 3130000 +8 halt 29 3140000 +8 halt 30 3140000 +# point pass=8 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997946 +9 run 1 3150000 +9 run 2 3150000 +9 run 3 3150000 +9 run 4 3150000 +9 run 5 3150000 +9 run 6 3160000 +9 run 7 3150000 +9 run 8 3150000 +9 run 9 3150000 +9 run 10 3150000 +9 run 11 3150000 +9 run 12 3150000 +9 run 13 3150000 +9 run 14 3160000 +9 run 15 3140000 +9 run 16 3150000 +9 run 17 3150000 +9 run 18 3140000 +9 run 19 3150000 +9 run 20 3150000 +9 run 21 3150000 +9 run 22 3150000 +9 run 23 3150000 +9 run 24 3140000 +9 run 25 3150000 +9 run 26 3150000 +9 run 27 3150000 +9 run 28 3150000 +9 run 29 3160000 +9 run 30 3150000 +# point pass=9 state=run jobs_per_s=48542.7 updates_per_s=24853862.8 clock_hz=99997945 +9 halt 1 3140000 +9 halt 2 3150000 +9 halt 3 3150000 +9 halt 4 3140000 +9 halt 5 3140000 +9 halt 6 3140000 +9 halt 7 3150000 +9 halt 8 3140000 +9 halt 9 3150000 +9 halt 10 3140000 +9 halt 11 3160000 +9 halt 12 3130000 +9 halt 13 3140000 +9 halt 14 3140000 +9 halt 15 3130000 +9 halt 16 3140000 +9 halt 17 3140000 +9 halt 18 3140000 +9 halt 19 3140000 +9 halt 20 3140000 +9 halt 21 3140000 +9 halt 22 3140000 +9 halt 23 3140000 +9 halt 24 3140000 +9 halt 25 3140000 +9 halt 26 3150000 +9 halt 27 3140000 +9 halt 28 3150000 +9 halt 29 3140000 +9 halt 30 3150000 +# point pass=9 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997946 +10 halt 1 3140000 +10 halt 2 3140000 +10 halt 3 3140000 +10 halt 4 3140000 +10 halt 5 3140000 +10 halt 6 3130000 +10 halt 7 3140000 +10 halt 8 3140000 +10 halt 9 3140000 +10 halt 10 3140000 +10 halt 11 3140000 +10 halt 12 3140000 +10 halt 13 3140000 +10 halt 14 3150000 +10 halt 15 3140000 +10 halt 16 3140000 +10 halt 17 3130000 +10 halt 18 3140000 +10 halt 19 3140000 +10 halt 20 3140000 +10 halt 21 3140000 +10 halt 22 3140000 +10 halt 23 3150000 +10 halt 24 3130000 +10 halt 25 3140000 +10 halt 26 3130000 +10 halt 27 3140000 +10 halt 28 3140000 +10 halt 29 3130000 +10 halt 30 3140000 +# point pass=10 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997947 +10 run 1 3150000 +10 run 2 3150000 +10 run 3 3150000 +10 run 4 3150000 +10 run 5 3150000 +10 run 6 3160000 +10 run 7 3140000 +10 run 8 3150000 +10 run 9 3150000 +10 run 10 3150000 +10 run 11 3160000 +10 run 12 3150000 +10 run 13 3150000 +10 run 14 3150000 +10 run 15 3180000 +10 run 16 3150000 +10 run 17 3140000 +10 run 18 3140000 +10 run 19 3170000 +10 run 20 3160000 +10 run 21 3150000 +10 run 22 3150000 +10 run 23 3150000 +10 run 24 3160000 +10 run 25 3150000 +10 run 26 3140000 +10 run 27 3150000 +10 run 28 3160000 +10 run 29 3150000 +10 run 30 3150000 +# point pass=10 state=run jobs_per_s=48542.7 updates_per_s=24853862.9 clock_hz=99997945 +# done SystemTime { tv_sec: 1793974380, tv_nsec: 187856982 } +# passes used 10 of 10 (dropped: []) +# run - halt = 11.0 +/- 1.8 mW (per-pass sd 5.7); 9/10 passes positive; 2 sample excursions beyond 6 MAD +# per-pass differences (mW): 10.0 10.0 20.0 10.0 10.0 0.0 20.0 10.0 10.0 10.0 +# board power while running 3144 mW; jobs/s 48542.7; updates/s 24853863.1; measured PL clock 99.998 MHz +# pre-registered prediction 12.0 mW: measured/predicted = 0.92, -0.6 sigma from it diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/null.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/null.dat new file mode 100644 index 000000000..3d3ea0f4a --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/null.dat @@ -0,0 +1,505 @@ +# class t0-recall cores 1 +# wai-quantum-fpga meter passes=8 settle=60s samples=30 null=true sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793969336, tv_nsec: 827384277 } +# pass state sample power_uW +1 halt 1 3130000 +1 halt 2 3120000 +1 halt 3 3130000 +1 halt 4 3140000 +1 halt 5 3530000 +1 halt 6 3150000 +1 halt 7 3130000 +1 halt 8 3130000 +1 halt 9 3130000 +1 halt 10 3130000 +1 halt 11 3140000 +1 halt 12 3140000 +1 halt 13 3130000 +1 halt 14 3150000 +1 halt 15 3710000 +1 halt 16 3130000 +1 halt 17 3130000 +1 halt 18 3130000 +1 halt 19 3130000 +1 halt 20 3120000 +1 halt 21 3140000 +1 halt 22 3150000 +1 halt 23 3120000 +1 halt 24 3130000 +1 halt 25 3130000 +1 halt 26 3120000 +1 halt 27 3130000 +1 halt 28 3120000 +1 halt 29 3140000 +1 halt 30 3120000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998017 +1 run 1 3130000 +1 run 2 3140000 +1 run 3 3140000 +1 run 4 3120000 +1 run 5 3140000 +1 run 6 3140000 +1 run 7 3120000 +1 run 8 3120000 +1 run 9 3120000 +1 run 10 3120000 +1 run 11 3150000 +1 run 12 3120000 +1 run 13 3130000 +1 run 14 3120000 +1 run 15 3130000 +1 run 16 3140000 +1 run 17 3120000 +1 run 18 3130000 +1 run 19 3130000 +1 run 20 3130000 +1 run 21 3130000 +1 run 22 3140000 +1 run 23 3120000 +1 run 24 3130000 +1 run 25 3120000 +1 run 26 3140000 +1 run 27 3120000 +1 run 28 3130000 +1 run 29 3130000 +1 run 30 3130000 +# point pass=1 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998017 +2 run 1 3150000 +2 run 2 3140000 +2 run 3 3140000 +2 run 4 3130000 +2 run 5 3140000 +2 run 6 3140000 +2 run 7 3140000 +2 run 8 3120000 +2 run 9 3150000 +2 run 10 3140000 +2 run 11 3130000 +2 run 12 3120000 +2 run 13 3130000 +2 run 14 3130000 +2 run 15 3140000 +2 run 16 3140000 +2 run 17 3120000 +2 run 18 3140000 +2 run 19 3140000 +2 run 20 3130000 +2 run 21 3140000 +2 run 22 3140000 +2 run 23 3140000 +2 run 24 3150000 +2 run 25 3140000 +2 run 26 3140000 +2 run 27 3140000 +2 run 28 3130000 +2 run 29 3120000 +2 run 30 3140000 +# point pass=2 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998017 +2 halt 1 3140000 +2 halt 2 3140000 +2 halt 3 3140000 +2 halt 4 3130000 +2 halt 5 3130000 +2 halt 6 3150000 +2 halt 7 3120000 +2 halt 8 3130000 +2 halt 9 3120000 +2 halt 10 3130000 +2 halt 11 3140000 +2 halt 12 3130000 +2 halt 13 3140000 +2 halt 14 3140000 +2 halt 15 3130000 +2 halt 16 3120000 +2 halt 17 3120000 +2 halt 18 3130000 +2 halt 19 3120000 +2 halt 20 3140000 +2 halt 21 3150000 +2 halt 22 3140000 +2 halt 23 3130000 +2 halt 24 3130000 +2 halt 25 3140000 +2 halt 26 3130000 +2 halt 27 3120000 +2 halt 28 3130000 +2 halt 29 3450000 +2 halt 30 3120000 +# point pass=2 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998017 +3 run 1 3130000 +3 run 2 3130000 +3 run 3 3130000 +3 run 4 3120000 +3 run 5 3130000 +3 run 6 3120000 +3 run 7 3130000 +3 run 8 3130000 +3 run 9 3120000 +3 run 10 3140000 +3 run 11 3150000 +3 run 12 3120000 +3 run 13 3140000 +3 run 14 3130000 +3 run 15 3120000 +3 run 16 3130000 +3 run 17 3140000 +3 run 18 3110000 +3 run 19 3140000 +3 run 20 3130000 +3 run 21 3120000 +3 run 22 3140000 +3 run 23 3140000 +3 run 24 3130000 +3 run 25 3130000 +3 run 26 3130000 +3 run 27 3150000 +3 run 28 3120000 +3 run 29 3130000 +3 run 30 3120000 +# point pass=3 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998016 +3 halt 1 3120000 +3 halt 2 3120000 +3 halt 3 3120000 +3 halt 4 3120000 +3 halt 5 3110000 +3 halt 6 3110000 +3 halt 7 3140000 +3 halt 8 3120000 +3 halt 9 3110000 +3 halt 10 3130000 +3 halt 11 3110000 +3 halt 12 3120000 +3 halt 13 3120000 +3 halt 14 3120000 +3 halt 15 3140000 +3 halt 16 3140000 +3 halt 17 3110000 +3 halt 18 3130000 +3 halt 19 3120000 +3 halt 20 3120000 +3 halt 21 3120000 +3 halt 22 3120000 +3 halt 23 3120000 +3 halt 24 3140000 +3 halt 25 3110000 +3 halt 26 3120000 +3 halt 27 3120000 +3 halt 28 3120000 +3 halt 29 3110000 +3 halt 30 3110000 +# point pass=3 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998016 +4 halt 1 3120000 +4 halt 2 3130000 +4 halt 3 3130000 +4 halt 4 3140000 +4 halt 5 3130000 +4 halt 6 3130000 +4 halt 7 3110000 +4 halt 8 3130000 +4 halt 9 3120000 +4 halt 10 3120000 +4 halt 11 3140000 +4 halt 12 3140000 +4 halt 13 3140000 +4 halt 14 3130000 +4 halt 15 3120000 +4 halt 16 3120000 +4 halt 17 3120000 +4 halt 18 3130000 +4 halt 19 3120000 +4 halt 20 3120000 +4 halt 21 3120000 +4 halt 22 3120000 +4 halt 23 3130000 +4 halt 24 3130000 +4 halt 25 3120000 +4 halt 26 3120000 +4 halt 27 3110000 +4 halt 28 3120000 +4 halt 29 3120000 +4 halt 30 3120000 +# point pass=4 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998016 +4 run 1 3150000 +4 run 2 3130000 +4 run 3 3130000 +4 run 4 3130000 +4 run 5 3140000 +4 run 6 3130000 +4 run 7 3130000 +4 run 8 3140000 +4 run 9 3150000 +4 run 10 3140000 +4 run 11 3140000 +4 run 12 3130000 +4 run 13 3140000 +4 run 14 3140000 +4 run 15 3140000 +4 run 16 3140000 +4 run 17 3130000 +4 run 18 3130000 +4 run 19 3130000 +4 run 20 3140000 +4 run 21 3150000 +4 run 22 3130000 +4 run 23 3130000 +4 run 24 3150000 +4 run 25 3140000 +4 run 26 3130000 +4 run 27 3140000 +4 run 28 3130000 +4 run 29 3130000 +4 run 30 3130000 +# point pass=4 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998016 +5 run 1 3140000 +5 run 2 3140000 +5 run 3 3140000 +5 run 4 3140000 +5 run 5 3140000 +5 run 6 3150000 +5 run 7 3140000 +5 run 8 3140000 +5 run 9 3140000 +5 run 10 3140000 +5 run 11 3140000 +5 run 12 3130000 +5 run 13 3140000 +5 run 14 3140000 +5 run 15 3130000 +5 run 16 3140000 +5 run 17 3130000 +5 run 18 3130000 +5 run 19 3140000 +5 run 20 3140000 +5 run 21 3150000 +5 run 22 3140000 +5 run 23 3140000 +5 run 24 3140000 +5 run 25 3130000 +5 run 26 3130000 +5 run 27 3150000 +5 run 28 3130000 +5 run 29 3150000 +5 run 30 3140000 +# point pass=5 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998015 +5 halt 1 3120000 +5 halt 2 3130000 +5 halt 3 3140000 +5 halt 4 3140000 +5 halt 5 3130000 +5 halt 6 3130000 +5 halt 7 3120000 +5 halt 8 3130000 +5 halt 9 3130000 +5 halt 10 3120000 +5 halt 11 3130000 +5 halt 12 3140000 +5 halt 13 3140000 +5 halt 14 3140000 +5 halt 15 3120000 +5 halt 16 3130000 +5 halt 17 3140000 +5 halt 18 3140000 +5 halt 19 3130000 +5 halt 20 3130000 +5 halt 21 3120000 +5 halt 22 3140000 +5 halt 23 3120000 +5 halt 24 3120000 +5 halt 25 3130000 +5 halt 26 3140000 +5 halt 27 3120000 +5 halt 28 3120000 +5 halt 29 3120000 +5 halt 30 3130000 +# point pass=5 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998015 +6 halt 1 3130000 +6 halt 2 3140000 +6 halt 3 3120000 +6 halt 4 3120000 +6 halt 5 3130000 +6 halt 6 3140000 +6 halt 7 3140000 +6 halt 8 3130000 +6 halt 9 3120000 +6 halt 10 3140000 +6 halt 11 3120000 +6 halt 12 3130000 +6 halt 13 3120000 +6 halt 14 3120000 +6 halt 15 3120000 +6 halt 16 3130000 +6 halt 17 3120000 +6 halt 18 3120000 +6 halt 19 3140000 +6 halt 20 3140000 +6 halt 21 3150000 +6 halt 22 3130000 +6 halt 23 3140000 +6 halt 24 3130000 +6 halt 25 3140000 +6 halt 26 3120000 +6 halt 27 3140000 +6 halt 28 3140000 +6 halt 29 3120000 +6 halt 30 3130000 +# point pass=6 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998016 +6 run 1 3110000 +6 run 2 3120000 +6 run 3 3130000 +6 run 4 3140000 +6 run 5 3150000 +6 run 6 3140000 +6 run 7 3140000 +6 run 8 3140000 +6 run 9 3140000 +6 run 10 3130000 +6 run 11 3130000 +6 run 12 3140000 +6 run 13 3180000 +6 run 14 3140000 +6 run 15 3140000 +6 run 16 3140000 +6 run 17 3140000 +6 run 18 3550000 +6 run 19 3130000 +6 run 20 3130000 +6 run 21 3140000 +6 run 22 3140000 +6 run 23 3140000 +6 run 24 3140000 +6 run 25 3140000 +6 run 26 3140000 +6 run 27 3150000 +6 run 28 3150000 +6 run 29 3140000 +6 run 30 3130000 +# point pass=6 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998017 +7 run 1 3140000 +7 run 2 3140000 +7 run 3 3140000 +7 run 4 3140000 +7 run 5 3130000 +7 run 6 3140000 +7 run 7 3130000 +7 run 8 3140000 +7 run 9 3140000 +7 run 10 3130000 +7 run 11 3140000 +7 run 12 3140000 +7 run 13 3140000 +7 run 14 3140000 +7 run 15 3150000 +7 run 16 3150000 +7 run 17 3140000 +7 run 18 3140000 +7 run 19 3130000 +7 run 20 3140000 +7 run 21 3140000 +7 run 22 3140000 +7 run 23 3160000 +7 run 24 3150000 +7 run 25 3130000 +7 run 26 3140000 +7 run 27 3140000 +7 run 28 3140000 +7 run 29 3140000 +7 run 30 3140000 +# point pass=7 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998016 +7 halt 1 3130000 +7 halt 2 3140000 +7 halt 3 3140000 +7 halt 4 3140000 +7 halt 5 3140000 +7 halt 6 3140000 +7 halt 7 3140000 +7 halt 8 3140000 +7 halt 9 3140000 +7 halt 10 3130000 +7 halt 11 3140000 +7 halt 12 3150000 +7 halt 13 3130000 +7 halt 14 3140000 +7 halt 15 3140000 +7 halt 16 3140000 +7 halt 17 3140000 +7 halt 18 3130000 +7 halt 19 3140000 +7 halt 20 3160000 +7 halt 21 3140000 +7 halt 22 3140000 +7 halt 23 3140000 +7 halt 24 3140000 +7 halt 25 3140000 +7 halt 26 3130000 +7 halt 27 3140000 +7 halt 28 3140000 +7 halt 29 3130000 +7 halt 30 3140000 +# point pass=7 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997626 +8 run 1 3140000 +8 run 2 3140000 +8 run 3 3140000 +8 run 4 3140000 +8 run 5 3140000 +8 run 6 3140000 +8 run 7 3130000 +8 run 8 3150000 +8 run 9 3150000 +8 run 10 3120000 +8 run 11 3140000 +8 run 12 3140000 +8 run 13 3140000 +8 run 14 3130000 +8 run 15 3140000 +8 run 16 3140000 +8 run 17 3150000 +8 run 18 3130000 +8 run 19 3140000 +8 run 20 3130000 +8 run 21 3140000 +8 run 22 3140000 +8 run 23 3130000 +8 run 24 3140000 +8 run 25 3140000 +8 run 26 3140000 +8 run 27 3140000 +8 run 28 3130000 +8 run 29 3140000 +8 run 30 3140000 +# point pass=8 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997571 +8 halt 1 3120000 +8 halt 2 3130000 +8 halt 3 3140000 +8 halt 4 3130000 +8 halt 5 3140000 +8 halt 6 3140000 +8 halt 7 3130000 +8 halt 8 3130000 +8 halt 9 3120000 +8 halt 10 3140000 +8 halt 11 3130000 +8 halt 12 3120000 +8 halt 13 3140000 +8 halt 14 3150000 +8 halt 15 3140000 +8 halt 16 3130000 +8 halt 17 3130000 +8 halt 18 3130000 +8 halt 19 3130000 +8 halt 20 3120000 +8 halt 21 3120000 +8 halt 22 3120000 +8 halt 23 3140000 +8 halt 24 3130000 +8 halt 25 3130000 +8 halt 26 3120000 +8 halt 27 3140000 +8 halt 28 3140000 +8 halt 29 3140000 +8 halt 30 3140000 +# point pass=8 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997630 +# done SystemTime { tv_sec: 1793970777, tv_nsec: 754326175 } +# passes used 8 of 8 (dropped: []) +# run - halt = 8.1 +/- 1.9 mW (per-pass sd 5.3); 6/8 passes positive; 5 sample excursions beyond 6 MAD +# per-pass differences (mW): 0.0 10.0 10.0 15.0 10.0 10.0 0.0 10.0 +# board power while running 3137 mW; jobs/s 0.0; updates/s 0.0; measured PL clock 99.998 MHz +# pre-registered prediction 0.0 mW: measured/predicted = inf, 4.3 sigma from it diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/step.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/step.dat new file mode 100644 index 000000000..72f5094c1 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/step.dat @@ -0,0 +1,610 @@ +# class t0-recall cores 1 +# wai-quantum-fpga meter passes=1 settle=0s samples=300 null=false sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793968736, tv_nsec: 357321553 } +# pass state sample power_uW +1 run 1 3530000 +1 run 2 3300000 +1 run 3 3290000 +1 run 4 3300000 +1 run 5 3310000 +1 run 6 3300000 +1 run 7 3300000 +1 run 8 3300000 +1 run 9 3300000 +1 run 10 3320000 +1 run 11 3300000 +1 run 12 3300000 +1 run 13 3300000 +1 run 14 3310000 +1 run 15 3290000 +1 run 16 3300000 +1 run 17 3320000 +1 run 18 3290000 +1 run 19 3300000 +1 run 20 3320000 +1 run 21 3300000 +1 run 22 3300000 +1 run 23 3300000 +1 run 24 3300000 +1 run 25 3300000 +1 run 26 3310000 +1 run 27 3320000 +1 run 28 3300000 +1 run 29 3300000 +1 run 30 3290000 +1 run 31 3290000 +1 run 32 3310000 +1 run 33 3300000 +1 run 34 3320000 +1 run 35 3300000 +1 run 36 3310000 +1 run 37 3300000 +1 run 38 3320000 +1 run 39 3320000 +1 run 40 3300000 +1 run 41 3300000 +1 run 42 3310000 +1 run 43 3300000 +1 run 44 3300000 +1 run 45 3300000 +1 run 46 3300000 +1 run 47 3320000 +1 run 48 3300000 +1 run 49 3300000 +1 run 50 3300000 +1 run 51 3300000 +1 run 52 3300000 +1 run 53 3300000 +1 run 54 3300000 +1 run 55 3310000 +1 run 56 3300000 +1 run 57 3300000 +1 run 58 3300000 +1 run 59 3310000 +1 run 60 3300000 +1 run 61 3290000 +1 run 62 3360000 +1 run 63 3300000 +1 run 64 3300000 +1 run 65 3290000 +1 run 66 3300000 +1 run 67 3300000 +1 run 68 3290000 +1 run 69 3300000 +1 run 70 3320000 +1 run 71 3330000 +1 run 72 3300000 +1 run 73 3320000 +1 run 74 3310000 +1 run 75 3310000 +1 run 76 3340000 +1 run 77 3300000 +1 run 78 3310000 +1 run 79 3320000 +1 run 80 3300000 +1 run 81 3300000 +1 run 82 3300000 +1 run 83 3310000 +1 run 84 3300000 +1 run 85 3290000 +1 run 86 3290000 +1 run 87 3300000 +1 run 88 3300000 +1 run 89 3300000 +1 run 90 3290000 +1 run 91 3300000 +1 run 92 3320000 +1 run 93 3300000 +1 run 94 3320000 +1 run 95 3300000 +1 run 96 3300000 +1 run 97 3300000 +1 run 98 3300000 +1 run 99 3300000 +1 run 100 3310000 +1 run 101 3300000 +1 run 102 3600000 +1 run 103 3300000 +1 run 104 3300000 +1 run 105 3310000 +1 run 106 3310000 +1 run 107 3300000 +1 run 108 3310000 +1 run 109 3300000 +1 run 110 3290000 +1 run 111 3300000 +1 run 112 3300000 +1 run 113 3310000 +1 run 114 3310000 +1 run 115 3300000 +1 run 116 3300000 +1 run 117 3320000 +1 run 118 3300000 +1 run 119 3300000 +1 run 120 3300000 +1 run 121 3330000 +1 run 122 3290000 +1 run 123 3290000 +1 run 124 3290000 +1 run 125 3320000 +1 run 126 3310000 +1 run 127 3280000 +1 run 128 3300000 +1 run 129 3300000 +1 run 130 3300000 +1 run 131 3290000 +1 run 132 3290000 +1 run 133 3290000 +1 run 134 3300000 +1 run 135 3290000 +1 run 136 3300000 +1 run 137 3290000 +1 run 138 3300000 +1 run 139 3300000 +1 run 140 3290000 +1 run 141 3290000 +1 run 142 3290000 +1 run 143 3290000 +1 run 144 3300000 +1 run 145 3290000 +1 run 146 3290000 +1 run 147 3300000 +1 run 148 3290000 +1 run 149 3320000 +1 run 150 3300000 +1 run 151 3290000 +1 run 152 3300000 +1 run 153 3290000 +1 run 154 3290000 +1 run 155 3310000 +1 run 156 3300000 +1 run 157 3320000 +1 run 158 3300000 +1 run 159 3310000 +1 run 160 3330000 +1 run 161 3290000 +1 run 162 3290000 +1 run 163 3310000 +1 run 164 3300000 +1 run 165 3290000 +1 run 166 3300000 +1 run 167 3300000 +1 run 168 3300000 +1 run 169 3290000 +1 run 170 3300000 +1 run 171 3290000 +1 run 172 3290000 +1 run 173 3290000 +1 run 174 3310000 +1 run 175 3300000 +1 run 176 3290000 +1 run 177 3290000 +1 run 178 3290000 +1 run 179 3300000 +1 run 180 3300000 +1 run 181 3320000 +1 run 182 3300000 +1 run 183 3300000 +1 run 184 3310000 +1 run 185 3300000 +1 run 186 3310000 +1 run 187 3300000 +1 run 188 3320000 +1 run 189 3320000 +1 run 190 3300000 +1 run 191 3310000 +1 run 192 3300000 +1 run 193 3300000 +1 run 194 3300000 +1 run 195 3300000 +1 run 196 3300000 +1 run 197 3300000 +1 run 198 3310000 +1 run 199 3310000 +1 run 200 3290000 +1 run 201 3300000 +1 run 202 3320000 +1 run 203 3300000 +1 run 204 3320000 +1 run 205 3310000 +1 run 206 3310000 +1 run 207 3300000 +1 run 208 3310000 +1 run 209 3300000 +1 run 210 3310000 +1 run 211 3300000 +1 run 212 3320000 +1 run 213 3330000 +1 run 214 3310000 +1 run 215 3310000 +1 run 216 3310000 +1 run 217 3300000 +1 run 218 3310000 +1 run 219 3300000 +1 run 220 3290000 +1 run 221 3320000 +1 run 222 3300000 +1 run 223 3300000 +1 run 224 3290000 +1 run 225 3300000 +1 run 226 3300000 +1 run 227 3300000 +1 run 228 3300000 +1 run 229 3310000 +1 run 230 3310000 +1 run 231 3300000 +1 run 232 3300000 +1 run 233 3300000 +1 run 234 3300000 +1 run 235 3300000 +1 run 236 3320000 +1 run 237 3290000 +1 run 238 3300000 +1 run 239 3310000 +1 run 240 3300000 +1 run 241 3310000 +1 run 242 3320000 +1 run 243 3300000 +1 run 244 3320000 +1 run 245 3310000 +1 run 246 3300000 +1 run 247 3300000 +1 run 248 3300000 +1 run 249 3310000 +1 run 250 3300000 +1 run 251 3310000 +1 run 252 3300000 +1 run 253 3320000 +1 run 254 3310000 +1 run 255 3290000 +1 run 256 3300000 +1 run 257 3310000 +1 run 258 3300000 +1 run 259 3310000 +1 run 260 3310000 +1 run 261 3310000 +1 run 262 3300000 +1 run 263 3300000 +1 run 264 3300000 +1 run 265 3300000 +1 run 266 3300000 +1 run 267 3320000 +1 run 268 3320000 +1 run 269 3300000 +1 run 270 3300000 +1 run 271 3310000 +1 run 272 3300000 +1 run 273 3300000 +1 run 274 3300000 +1 run 275 3300000 +1 run 276 3330000 +1 run 277 3310000 +1 run 278 3310000 +1 run 279 3300000 +1 run 280 3300000 +1 run 281 3300000 +1 run 282 3300000 +1 run 283 3310000 +1 run 284 3310000 +1 run 285 3310000 +1 run 286 3310000 +1 run 287 3290000 +1 run 288 3310000 +1 run 289 3310000 +1 run 290 3300000 +1 run 291 3320000 +1 run 292 3300000 +1 run 293 3300000 +1 run 294 3320000 +1 run 295 3310000 +1 run 296 3300000 +1 run 297 3300000 +1 run 298 3300000 +1 run 299 3660000 +1 run 300 3330000 +# point pass=1 state=run jobs_per_s=12499751.7 updates_per_s=0.0 clock_hz=99998011 +1 halt 1 3370000 +1 halt 2 3140000 +1 halt 3 3140000 +1 halt 4 3140000 +1 halt 5 3130000 +1 halt 6 3130000 +1 halt 7 3140000 +1 halt 8 3150000 +1 halt 9 3160000 +1 halt 10 3140000 +1 halt 11 3140000 +1 halt 12 3140000 +1 halt 13 3140000 +1 halt 14 3550000 +1 halt 15 3130000 +1 halt 16 3140000 +1 halt 17 3140000 +1 halt 18 3140000 +1 halt 19 3130000 +1 halt 20 3140000 +1 halt 21 3140000 +1 halt 22 3160000 +1 halt 23 3150000 +1 halt 24 3140000 +1 halt 25 3140000 +1 halt 26 3150000 +1 halt 27 3140000 +1 halt 28 3140000 +1 halt 29 3140000 +1 halt 30 3140000 +1 halt 31 3140000 +1 halt 32 3140000 +1 halt 33 3150000 +1 halt 34 3140000 +1 halt 35 3150000 +1 halt 36 3150000 +1 halt 37 3140000 +1 halt 38 3140000 +1 halt 39 3140000 +1 halt 40 3140000 +1 halt 41 3150000 +1 halt 42 3140000 +1 halt 43 3140000 +1 halt 44 3150000 +1 halt 45 3150000 +1 halt 46 3160000 +1 halt 47 3130000 +1 halt 48 3140000 +1 halt 49 3140000 +1 halt 50 3140000 +1 halt 51 3140000 +1 halt 52 3140000 +1 halt 53 3140000 +1 halt 54 3150000 +1 halt 55 3130000 +1 halt 56 3130000 +1 halt 57 3140000 +1 halt 58 3130000 +1 halt 59 3140000 +1 halt 60 3140000 +1 halt 61 3140000 +1 halt 62 3140000 +1 halt 63 3140000 +1 halt 64 3140000 +1 halt 65 3140000 +1 halt 66 3140000 +1 halt 67 3140000 +1 halt 68 3140000 +1 halt 69 3150000 +1 halt 70 3140000 +1 halt 71 3140000 +1 halt 72 3150000 +1 halt 73 3140000 +1 halt 74 3140000 +1 halt 75 3140000 +1 halt 76 3140000 +1 halt 77 3150000 +1 halt 78 3150000 +1 halt 79 3140000 +1 halt 80 3140000 +1 halt 81 3140000 +1 halt 82 3130000 +1 halt 83 3130000 +1 halt 84 3140000 +1 halt 85 3140000 +1 halt 86 3150000 +1 halt 87 3140000 +1 halt 88 3150000 +1 halt 89 3150000 +1 halt 90 3140000 +1 halt 91 3130000 +1 halt 92 3140000 +1 halt 93 3130000 +1 halt 94 3140000 +1 halt 95 3140000 +1 halt 96 3140000 +1 halt 97 3140000 +1 halt 98 3140000 +1 halt 99 3140000 +1 halt 100 3140000 +1 halt 101 3150000 +1 halt 102 3140000 +1 halt 103 3140000 +1 halt 104 3140000 +1 halt 105 3140000 +1 halt 106 3140000 +1 halt 107 3140000 +1 halt 108 3140000 +1 halt 109 3150000 +1 halt 110 3140000 +1 halt 111 3130000 +1 halt 112 3140000 +1 halt 113 3140000 +1 halt 114 3140000 +1 halt 115 3140000 +1 halt 116 3140000 +1 halt 117 3150000 +1 halt 118 3140000 +1 halt 119 3140000 +1 halt 120 3130000 +1 halt 121 3140000 +1 halt 122 3140000 +1 halt 123 3140000 +1 halt 124 3150000 +1 halt 125 3140000 +1 halt 126 3140000 +1 halt 127 3140000 +1 halt 128 3140000 +1 halt 129 3140000 +1 halt 130 3150000 +1 halt 131 3140000 +1 halt 132 3150000 +1 halt 133 3150000 +1 halt 134 3140000 +1 halt 135 3130000 +1 halt 136 3140000 +1 halt 137 3140000 +1 halt 138 3140000 +1 halt 139 3140000 +1 halt 140 3140000 +1 halt 141 3150000 +1 halt 142 3140000 +1 halt 143 3140000 +1 halt 144 3140000 +1 halt 145 3130000 +1 halt 146 3150000 +1 halt 147 3140000 +1 halt 148 3140000 +1 halt 149 3140000 +1 halt 150 3130000 +1 halt 151 3140000 +1 halt 152 3130000 +1 halt 153 3140000 +1 halt 154 3140000 +1 halt 155 3130000 +1 halt 156 3150000 +1 halt 157 3140000 +1 halt 158 3130000 +1 halt 159 3140000 +1 halt 160 3140000 +1 halt 161 3140000 +1 halt 162 3150000 +1 halt 163 3140000 +1 halt 164 3160000 +1 halt 165 3150000 +1 halt 166 3130000 +1 halt 167 3140000 +1 halt 168 3140000 +1 halt 169 3130000 +1 halt 170 3140000 +1 halt 171 3140000 +1 halt 172 3140000 +1 halt 173 3140000 +1 halt 174 3140000 +1 halt 175 3160000 +1 halt 176 3140000 +1 halt 177 3130000 +1 halt 178 3130000 +1 halt 179 3150000 +1 halt 180 3150000 +1 halt 181 3140000 +1 halt 182 3140000 +1 halt 183 3140000 +1 halt 184 3140000 +1 halt 185 3140000 +1 halt 186 3140000 +1 halt 187 3150000 +1 halt 188 3150000 +1 halt 189 3130000 +1 halt 190 3130000 +1 halt 191 3140000 +1 halt 192 3140000 +1 halt 193 3130000 +1 halt 194 3140000 +1 halt 195 3150000 +1 halt 196 3150000 +1 halt 197 3140000 +1 halt 198 3130000 +1 halt 199 3140000 +1 halt 200 3140000 +1 halt 201 3140000 +1 halt 202 3140000 +1 halt 203 3140000 +1 halt 204 3150000 +1 halt 205 3140000 +1 halt 206 3140000 +1 halt 207 3130000 +1 halt 208 3140000 +1 halt 209 3140000 +1 halt 210 3140000 +1 halt 211 3160000 +1 halt 212 3140000 +1 halt 213 3130000 +1 halt 214 3140000 +1 halt 215 3140000 +1 halt 216 3130000 +1 halt 217 3140000 +1 halt 218 3140000 +1 halt 219 3160000 +1 halt 220 3150000 +1 halt 221 3140000 +1 halt 222 3140000 +1 halt 223 3140000 +1 halt 224 3130000 +1 halt 225 3140000 +1 halt 226 3140000 +1 halt 227 3130000 +1 halt 228 3150000 +1 halt 229 3140000 +1 halt 230 3140000 +1 halt 231 3130000 +1 halt 232 3130000 +1 halt 233 3150000 +1 halt 234 3140000 +1 halt 235 3140000 +1 halt 236 3130000 +1 halt 237 3140000 +1 halt 238 3130000 +1 halt 239 3130000 +1 halt 240 3140000 +1 halt 241 3140000 +1 halt 242 3140000 +1 halt 243 3150000 +1 halt 244 3130000 +1 halt 245 3140000 +1 halt 246 3140000 +1 halt 247 3140000 +1 halt 248 3140000 +1 halt 249 3140000 +1 halt 250 3140000 +1 halt 251 3140000 +1 halt 252 3130000 +1 halt 253 3120000 +1 halt 254 3130000 +1 halt 255 3130000 +1 halt 256 3120000 +1 halt 257 3140000 +1 halt 258 3120000 +1 halt 259 3140000 +1 halt 260 3130000 +1 halt 261 3130000 +1 halt 262 3120000 +1 halt 263 3120000 +1 halt 264 3130000 +1 halt 265 3130000 +1 halt 266 3140000 +1 halt 267 3130000 +1 halt 268 3140000 +1 halt 269 3140000 +1 halt 270 3130000 +1 halt 271 3130000 +1 halt 272 3130000 +1 halt 273 3130000 +1 halt 274 3150000 +1 halt 275 3150000 +1 halt 276 3120000 +1 halt 277 3130000 +1 halt 278 3130000 +1 halt 279 3130000 +1 halt 280 3130000 +1 halt 281 3120000 +1 halt 282 3140000 +1 halt 283 3150000 +1 halt 284 3130000 +1 halt 285 3130000 +1 halt 286 3120000 +1 halt 287 3130000 +1 halt 288 3120000 +1 halt 289 3130000 +1 halt 290 3120000 +1 halt 291 3150000 +1 halt 292 3120000 +1 halt 293 3130000 +1 halt 294 3120000 +1 halt 295 3130000 +1 halt 296 3130000 +1 halt 297 3120000 +1 halt 298 3140000 +1 halt 299 3140000 +1 halt 300 3130000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998009 +# done SystemTime { tv_sec: 1793969336, tv_nsec: 762934283 } +# passes used 1 of 1 (dropped: []) +# run - halt = 160.0 +/- NaN mW (per-pass sd NaN); 1/1 passes positive; 7 sample excursions beyond 6 MAD +# per-pass differences (mW): 160.0 +# board power while running 3300 mW; jobs/s 12499751.7; updates/s 0.0; measured PL clock 99.998 MHz diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/t0-idle.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/t0-idle.dat new file mode 100644 index 000000000..094db6fd1 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/t0-idle.dat @@ -0,0 +1,505 @@ +# class t0-idle cores 1 +# wai-quantum-fpga meter passes=8 settle=60s samples=30 null=false sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793974380, tv_nsec: 248171637 } +# pass state sample power_uW +1 run 1 3130000 +1 run 2 3140000 +1 run 3 3130000 +1 run 4 3140000 +1 run 5 3140000 +1 run 6 3140000 +1 run 7 3150000 +1 run 8 3140000 +1 run 9 3150000 +1 run 10 3140000 +1 run 11 3150000 +1 run 12 3150000 +1 run 13 3140000 +1 run 14 3140000 +1 run 15 3150000 +1 run 16 3140000 +1 run 17 3150000 +1 run 18 3150000 +1 run 19 3140000 +1 run 20 3150000 +1 run 21 3140000 +1 run 22 3160000 +1 run 23 3140000 +1 run 24 3150000 +1 run 25 3150000 +1 run 26 3150000 +1 run 27 3150000 +1 run 28 3140000 +1 run 29 3140000 +1 run 30 3150000 +# point pass=1 state=run jobs_per_s=33332655.7 updates_per_s=0.0 clock_hz=99997945 +1 halt 1 3130000 +1 halt 2 3140000 +1 halt 3 3130000 +1 halt 4 3140000 +1 halt 5 3130000 +1 halt 6 3130000 +1 halt 7 3130000 +1 halt 8 3130000 +1 halt 9 3130000 +1 halt 10 3120000 +1 halt 11 3120000 +1 halt 12 3140000 +1 halt 13 3130000 +1 halt 14 3130000 +1 halt 15 3130000 +1 halt 16 3140000 +1 halt 17 3120000 +1 halt 18 3140000 +1 halt 19 3140000 +1 halt 20 3140000 +1 halt 21 3130000 +1 halt 22 3130000 +1 halt 23 3130000 +1 halt 24 3140000 +1 halt 25 3120000 +1 halt 26 3130000 +1 halt 27 3140000 +1 halt 28 3140000 +1 halt 29 3130000 +1 halt 30 3140000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997945 +2 halt 1 3140000 +2 halt 2 3360000 +2 halt 3 3140000 +2 halt 4 3140000 +2 halt 5 3140000 +2 halt 6 3140000 +2 halt 7 3150000 +2 halt 8 3130000 +2 halt 9 3140000 +2 halt 10 3140000 +2 halt 11 3130000 +2 halt 12 3130000 +2 halt 13 3140000 +2 halt 14 3130000 +2 halt 15 3160000 +2 halt 16 3150000 +2 halt 17 3140000 +2 halt 18 3140000 +2 halt 19 3130000 +2 halt 20 3150000 +2 halt 21 3140000 +2 halt 22 3130000 +2 halt 23 3140000 +2 halt 24 3150000 +2 halt 25 3140000 +2 halt 26 3130000 +2 halt 27 3140000 +2 halt 28 3130000 +2 halt 29 3130000 +2 halt 30 3130000 +# point pass=2 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997945 +2 run 1 3120000 +2 run 2 3120000 +2 run 3 3130000 +2 run 4 3130000 +2 run 5 3110000 +2 run 6 3120000 +2 run 7 3150000 +2 run 8 3120000 +2 run 9 3140000 +2 run 10 3130000 +2 run 11 3130000 +2 run 12 3130000 +2 run 13 3120000 +2 run 14 3130000 +2 run 15 3140000 +2 run 16 3140000 +2 run 17 3130000 +2 run 18 3130000 +2 run 19 3130000 +2 run 20 3120000 +2 run 21 3130000 +2 run 22 3140000 +2 run 23 3140000 +2 run 24 3140000 +2 run 25 3130000 +2 run 26 3140000 +2 run 27 3130000 +2 run 28 3140000 +2 run 29 3120000 +2 run 30 3150000 +# point pass=2 state=run jobs_per_s=33332686.8 updates_per_s=0.0 clock_hz=99998037 +3 run 1 3140000 +3 run 2 3140000 +3 run 3 3130000 +3 run 4 3150000 +3 run 5 3140000 +3 run 6 3140000 +3 run 7 3130000 +3 run 8 3140000 +3 run 9 3140000 +3 run 10 3130000 +3 run 11 3140000 +3 run 12 3160000 +3 run 13 3140000 +3 run 14 3140000 +3 run 15 3150000 +3 run 16 3150000 +3 run 17 3120000 +3 run 18 3130000 +3 run 19 3130000 +3 run 20 3130000 +3 run 21 3540000 +3 run 22 3130000 +3 run 23 3130000 +3 run 24 3120000 +3 run 25 3120000 +3 run 26 3130000 +3 run 27 3150000 +3 run 28 3130000 +3 run 29 3130000 +3 run 30 3140000 +# point pass=3 state=run jobs_per_s=33332682.8 updates_per_s=0.0 clock_hz=99998024 +3 halt 1 3140000 +3 halt 2 3130000 +3 halt 3 3110000 +3 halt 4 3120000 +3 halt 5 3120000 +3 halt 6 3120000 +3 halt 7 3120000 +3 halt 8 3120000 +3 halt 9 3140000 +3 halt 10 3120000 +3 halt 11 3120000 +3 halt 12 3120000 +3 halt 13 3120000 +3 halt 14 3130000 +3 halt 15 3120000 +3 halt 16 3110000 +3 halt 17 3130000 +3 halt 18 3120000 +3 halt 19 3110000 +3 halt 20 3110000 +3 halt 21 3120000 +3 halt 22 3130000 +3 halt 23 3120000 +3 halt 24 3130000 +3 halt 25 3130000 +3 halt 26 3120000 +3 halt 27 3130000 +3 halt 28 3120000 +3 halt 29 3130000 +3 halt 30 3120000 +# point pass=3 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99998013 +4 run 1 3120000 +4 run 2 3120000 +4 run 3 3120000 +4 run 4 3140000 +4 run 5 3130000 +4 run 6 3140000 +4 run 7 3130000 +4 run 8 3120000 +4 run 9 3130000 +4 run 10 3130000 +4 run 11 3110000 +4 run 12 3130000 +4 run 13 3130000 +4 run 14 3130000 +4 run 15 3140000 +4 run 16 3120000 +4 run 17 3130000 +4 run 18 3120000 +4 run 19 3120000 +4 run 20 3140000 +4 run 21 3140000 +4 run 22 3120000 +4 run 23 3130000 +4 run 24 3130000 +4 run 25 3120000 +4 run 26 3120000 +4 run 27 3120000 +4 run 28 3140000 +4 run 29 3140000 +4 run 30 3130000 +# point pass=4 state=run jobs_per_s=33332675.3 updates_per_s=0.0 clock_hz=99998003 +4 halt 1 3130000 +4 halt 2 3120000 +4 halt 3 3140000 +4 halt 4 3130000 +4 halt 5 3140000 +4 halt 6 3140000 +4 halt 7 3130000 +4 halt 8 3130000 +4 halt 9 3150000 +4 halt 10 3130000 +4 halt 11 3140000 +4 halt 12 3140000 +4 halt 13 3130000 +4 halt 14 3140000 +4 halt 15 3140000 +4 halt 16 3130000 +4 halt 17 3160000 +4 halt 18 3150000 +4 halt 19 3140000 +4 halt 20 3140000 +4 halt 21 3130000 +4 halt 22 3140000 +4 halt 23 3140000 +4 halt 24 3130000 +4 halt 25 3140000 +4 halt 26 3150000 +4 halt 27 3140000 +4 halt 28 3140000 +4 halt 29 3140000 +4 halt 30 3140000 +# point pass=4 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997996 +5 halt 1 3140000 +5 halt 2 3130000 +5 halt 3 3130000 +5 halt 4 3140000 +5 halt 5 3140000 +5 halt 6 3150000 +5 halt 7 3140000 +5 halt 8 3140000 +5 halt 9 3130000 +5 halt 10 3150000 +5 halt 11 3130000 +5 halt 12 3140000 +5 halt 13 3140000 +5 halt 14 3150000 +5 halt 15 3140000 +5 halt 16 3140000 +5 halt 17 3140000 +5 halt 18 3140000 +5 halt 19 3140000 +5 halt 20 3130000 +5 halt 21 3140000 +5 halt 22 3140000 +5 halt 23 3130000 +5 halt 24 3140000 +5 halt 25 3160000 +5 halt 26 3120000 +5 halt 27 3120000 +5 halt 28 3120000 +5 halt 29 3130000 +5 halt 30 3130000 +# point pass=5 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997989 +5 run 1 3150000 +5 run 2 3150000 +5 run 3 3150000 +5 run 4 3140000 +5 run 5 3130000 +5 run 6 3150000 +5 run 7 3140000 +5 run 8 3140000 +5 run 9 3140000 +5 run 10 3130000 +5 run 11 3160000 +5 run 12 3140000 +5 run 13 3140000 +5 run 14 3140000 +5 run 15 3140000 +5 run 16 3140000 +5 run 17 3140000 +5 run 18 3140000 +5 run 19 3140000 +5 run 20 3150000 +5 run 21 3140000 +5 run 22 3140000 +5 run 23 3460000 +5 run 24 3140000 +5 run 25 3140000 +5 run 26 3150000 +5 run 27 3140000 +5 run 28 3140000 +5 run 29 3140000 +5 run 30 3140000 +# point pass=5 state=run jobs_per_s=33332668.6 updates_per_s=0.0 clock_hz=99997984 +6 halt 1 3140000 +6 halt 2 3140000 +6 halt 3 3140000 +6 halt 4 3140000 +6 halt 5 3130000 +6 halt 6 3140000 +6 halt 7 3140000 +6 halt 8 3150000 +6 halt 9 3130000 +6 halt 10 3140000 +6 halt 11 3130000 +6 halt 12 3130000 +6 halt 13 3130000 +6 halt 14 3140000 +6 halt 15 3130000 +6 halt 16 3130000 +6 halt 17 3140000 +6 halt 18 3140000 +6 halt 19 3140000 +6 halt 20 3140000 +6 halt 21 3130000 +6 halt 22 3150000 +6 halt 23 3150000 +6 halt 24 3130000 +6 halt 25 3140000 +6 halt 26 3140000 +6 halt 27 3140000 +6 halt 28 3140000 +6 halt 29 3140000 +6 halt 30 3130000 +# point pass=6 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997979 +6 run 1 3140000 +6 run 2 3130000 +6 run 3 3140000 +6 run 4 3130000 +6 run 5 3120000 +6 run 6 3130000 +6 run 7 3140000 +6 run 8 3140000 +6 run 9 3140000 +6 run 10 3130000 +6 run 11 3150000 +6 run 12 3130000 +6 run 13 3140000 +6 run 14 3130000 +6 run 15 3130000 +6 run 16 3130000 +6 run 17 3130000 +6 run 18 3130000 +6 run 19 3160000 +6 run 20 3150000 +6 run 21 3130000 +6 run 22 3140000 +6 run 23 3140000 +6 run 24 3140000 +6 run 25 3120000 +6 run 26 3120000 +6 run 27 3140000 +6 run 28 3150000 +6 run 29 3130000 +6 run 30 3120000 +# point pass=6 state=run jobs_per_s=33332666.1 updates_per_s=0.0 clock_hz=99997975 +7 run 1 3140000 +7 run 2 3130000 +7 run 3 3150000 +7 run 4 3140000 +7 run 5 3130000 +7 run 6 3150000 +7 run 7 3140000 +7 run 8 3150000 +7 run 9 3130000 +7 run 10 3140000 +7 run 11 3140000 +7 run 12 3130000 +7 run 13 3130000 +7 run 14 3140000 +7 run 15 3130000 +7 run 16 3150000 +7 run 17 3150000 +7 run 18 3140000 +7 run 19 3140000 +7 run 20 3120000 +7 run 21 3120000 +7 run 22 3140000 +7 run 23 3130000 +7 run 24 3130000 +7 run 25 3150000 +7 run 26 3130000 +7 run 27 3130000 +7 run 28 3120000 +7 run 29 3130000 +7 run 30 3130000 +# point pass=7 state=run jobs_per_s=33332665.0 updates_per_s=0.0 clock_hz=99997972 +7 halt 1 3120000 +7 halt 2 3140000 +7 halt 3 3130000 +7 halt 4 3140000 +7 halt 5 3140000 +7 halt 6 3130000 +7 halt 7 3140000 +7 halt 8 3130000 +7 halt 9 3130000 +7 halt 10 3140000 +7 halt 11 3130000 +7 halt 12 3130000 +7 halt 13 3140000 +7 halt 14 3130000 +7 halt 15 3140000 +7 halt 16 3130000 +7 halt 17 3140000 +7 halt 18 3120000 +7 halt 19 3120000 +7 halt 20 3130000 +7 halt 21 3120000 +7 halt 22 3130000 +7 halt 23 3130000 +7 halt 24 3120000 +7 halt 25 3120000 +7 halt 26 3120000 +7 halt 27 3140000 +7 halt 28 3150000 +7 halt 29 3120000 +7 halt 30 3140000 +# point pass=7 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997971 +8 run 1 3150000 +8 run 2 3150000 +8 run 3 3140000 +8 run 4 3140000 +8 run 5 3140000 +8 run 6 3130000 +8 run 7 3130000 +8 run 8 3150000 +8 run 9 3140000 +8 run 10 3150000 +8 run 11 3130000 +8 run 12 3130000 +8 run 13 3130000 +8 run 14 3140000 +8 run 15 3140000 +8 run 16 3140000 +8 run 17 3140000 +8 run 18 3120000 +8 run 19 3130000 +8 run 20 3120000 +8 run 21 3130000 +8 run 22 3140000 +8 run 23 3130000 +8 run 24 3130000 +8 run 25 3150000 +8 run 26 3130000 +8 run 27 3140000 +8 run 28 3140000 +8 run 29 3140000 +8 run 30 3130000 +# point pass=8 state=run jobs_per_s=33332663.2 updates_per_s=0.0 clock_hz=99997967 +8 halt 1 3130000 +8 halt 2 3130000 +8 halt 3 3130000 +8 halt 4 3130000 +8 halt 5 3140000 +8 halt 6 3120000 +8 halt 7 3120000 +8 halt 8 3140000 +8 halt 9 3150000 +8 halt 10 3130000 +8 halt 11 3140000 +8 halt 12 3120000 +8 halt 13 3120000 +8 halt 14 3130000 +8 halt 15 3140000 +8 halt 16 3130000 +8 halt 17 3140000 +8 halt 18 3130000 +8 halt 19 3140000 +8 halt 20 3140000 +8 halt 21 3140000 +8 halt 22 3140000 +8 halt 23 3120000 +8 halt 24 3140000 +8 halt 25 3130000 +8 halt 26 3130000 +8 halt 27 3120000 +8 halt 28 3120000 +8 halt 29 3140000 +8 halt 30 3140000 +# point pass=8 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997966 +# done SystemTime { tv_sec: 1793975821, tv_nsec: 177880976 } +# passes used 8 of 8 (dropped: []) +# run - halt = 1.9 +/- 4.0 mW (per-pass sd 11.3); 4/8 passes positive; 3 sample excursions beyond 6 MAD +# per-pass differences (mW): 10.0 -10.0 20.0 -10.0 0.0 -10.0 5.0 10.0 +# board power while running 3136 mW; jobs/s 33332670.4; updates/s 0.0; measured PL clock 99.998 MHz +# pre-registered prediction 4.0 mW: measured/predicted = 0.47, -0.5 sigma from it diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/t0-recall-50MHz.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/t0-recall-50MHz.dat new file mode 100644 index 000000000..46d24f582 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/t0-recall-50MHz.dat @@ -0,0 +1,381 @@ +# class t0-recall cores 1 +# wai-quantum-fpga meter passes=6 settle=60s samples=30 null=false sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793975822, tv_nsec: 342834432 } +# pass state sample power_uW +1 run 1 3190000 +1 run 2 3190000 +1 run 3 3610000 +1 run 4 3190000 +1 run 5 3200000 +1 run 6 3190000 +1 run 7 3190000 +1 run 8 3190000 +1 run 9 3190000 +1 run 10 3190000 +1 run 11 3180000 +1 run 12 3180000 +1 run 13 3200000 +1 run 14 3200000 +1 run 15 3180000 +1 run 16 3180000 +1 run 17 3180000 +1 run 18 3190000 +1 run 19 3180000 +1 run 20 3190000 +1 run 21 3190000 +1 run 22 3210000 +1 run 23 3180000 +1 run 24 3180000 +1 run 25 3190000 +1 run 26 3190000 +1 run 27 3190000 +1 run 28 3200000 +1 run 29 3190000 +1 run 30 3180000 +# point pass=1 state=run jobs_per_s=6249874.2 updates_per_s=0.0 clock_hz=49998982 +1 halt 1 3110000 +1 halt 2 3130000 +1 halt 3 3120000 +1 halt 4 3110000 +1 halt 5 3120000 +1 halt 6 3120000 +1 halt 7 3110000 +1 halt 8 3120000 +1 halt 9 3120000 +1 halt 10 3130000 +1 halt 11 3120000 +1 halt 12 3120000 +1 halt 13 3110000 +1 halt 14 3110000 +1 halt 15 3110000 +1 halt 16 3110000 +1 halt 17 3100000 +1 halt 18 3120000 +1 halt 19 3110000 +1 halt 20 3110000 +1 halt 21 3110000 +1 halt 22 3110000 +1 halt 23 3100000 +1 halt 24 3110000 +1 halt 25 3120000 +1 halt 26 3110000 +1 halt 27 3110000 +1 halt 28 3110000 +1 halt 29 3110000 +1 halt 30 3100000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=49998982 +2 halt 1 3110000 +2 halt 2 3110000 +2 halt 3 3110000 +2 halt 4 3110000 +2 halt 5 3110000 +2 halt 6 3110000 +2 halt 7 3130000 +2 halt 8 3110000 +2 halt 9 3110000 +2 halt 10 3100000 +2 halt 11 3110000 +2 halt 12 3110000 +2 halt 13 3110000 +2 halt 14 3110000 +2 halt 15 3110000 +2 halt 16 3110000 +2 halt 17 3100000 +2 halt 18 3110000 +2 halt 19 3110000 +2 halt 20 3100000 +2 halt 21 3120000 +2 halt 22 3130000 +2 halt 23 3110000 +2 halt 24 3100000 +2 halt 25 3110000 +2 halt 26 3120000 +2 halt 27 3100000 +2 halt 28 3120000 +2 halt 29 3120000 +2 halt 30 3140000 +# point pass=2 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=49998981 +2 run 1 3190000 +2 run 2 3190000 +2 run 3 3180000 +2 run 4 3200000 +2 run 5 3190000 +2 run 6 3180000 +2 run 7 3190000 +2 run 8 3190000 +2 run 9 3180000 +2 run 10 3180000 +2 run 11 3190000 +2 run 12 3190000 +2 run 13 3190000 +2 run 14 3180000 +2 run 15 3190000 +2 run 16 3190000 +2 run 17 3200000 +2 run 18 3190000 +2 run 19 3210000 +2 run 20 3190000 +2 run 21 3200000 +2 run 22 3190000 +2 run 23 3200000 +2 run 24 3200000 +2 run 25 3190000 +2 run 26 3190000 +2 run 27 3200000 +2 run 28 3200000 +2 run 29 3190000 +2 run 30 3190000 +# point pass=2 state=run jobs_per_s=6249874.0 updates_per_s=0.0 clock_hz=49998981 +3 halt 1 3110000 +3 halt 2 3110000 +3 halt 3 3110000 +3 halt 4 3100000 +3 halt 5 3110000 +3 halt 6 3110000 +3 halt 7 3120000 +3 halt 8 3110000 +3 halt 9 3120000 +3 halt 10 3110000 +3 halt 11 3110000 +3 halt 12 3110000 +3 halt 13 3120000 +3 halt 14 3120000 +3 halt 15 3130000 +3 halt 16 3130000 +3 halt 17 3110000 +3 halt 18 3120000 +3 halt 19 3110000 +3 halt 20 3120000 +3 halt 21 3120000 +3 halt 22 3110000 +3 halt 23 3110000 +3 halt 24 3130000 +3 halt 25 3110000 +3 halt 26 3120000 +3 halt 27 3100000 +3 halt 28 3110000 +3 halt 29 3110000 +3 halt 30 3110000 +# point pass=3 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=49998981 +3 run 1 3170000 +3 run 2 3170000 +3 run 3 3190000 +3 run 4 3200000 +3 run 5 3170000 +3 run 6 3170000 +3 run 7 3180000 +3 run 8 3170000 +3 run 9 3180000 +3 run 10 3170000 +3 run 11 3180000 +3 run 12 3190000 +3 run 13 3200000 +3 run 14 3170000 +3 run 15 3180000 +3 run 16 3180000 +3 run 17 3180000 +3 run 18 3180000 +3 run 19 3180000 +3 run 20 3180000 +3 run 21 3200000 +3 run 22 3190000 +3 run 23 3200000 +3 run 24 3190000 +3 run 25 3190000 +3 run 26 3190000 +3 run 27 3190000 +3 run 28 3200000 +3 run 29 3190000 +3 run 30 3190000 +# point pass=3 state=run jobs_per_s=6249873.9 updates_per_s=0.0 clock_hz=49998980 +4 halt 1 3120000 +4 halt 2 3100000 +4 halt 3 3100000 +4 halt 4 3100000 +4 halt 5 3100000 +4 halt 6 3100000 +4 halt 7 3110000 +4 halt 8 3100000 +4 halt 9 3110000 +4 halt 10 3100000 +4 halt 11 3100000 +4 halt 12 3100000 +4 halt 13 3100000 +4 halt 14 3100000 +4 halt 15 3100000 +4 halt 16 3100000 +4 halt 17 3110000 +4 halt 18 3100000 +4 halt 19 3100000 +4 halt 20 3100000 +4 halt 21 3090000 +4 halt 22 3110000 +4 halt 23 3110000 +4 halt 24 3100000 +4 halt 25 3100000 +4 halt 26 3100000 +4 halt 27 3100000 +4 halt 28 3090000 +4 halt 29 3100000 +4 halt 30 3100000 +# point pass=4 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=49998980 +4 run 1 3180000 +4 run 2 3190000 +4 run 3 3180000 +4 run 4 3190000 +4 run 5 3190000 +4 run 6 3200000 +4 run 7 3190000 +4 run 8 3190000 +4 run 9 3170000 +4 run 10 3190000 +4 run 11 3190000 +4 run 12 3190000 +4 run 13 3190000 +4 run 14 3190000 +4 run 15 3180000 +4 run 16 3190000 +4 run 17 3200000 +4 run 18 3190000 +4 run 19 3190000 +4 run 20 3200000 +4 run 21 3200000 +4 run 22 3190000 +4 run 23 3180000 +4 run 24 3180000 +4 run 25 3170000 +4 run 26 3180000 +4 run 27 3170000 +4 run 28 3180000 +4 run 29 3200000 +4 run 30 3210000 +# point pass=4 state=run jobs_per_s=6249873.9 updates_per_s=0.0 clock_hz=49998979 +5 halt 1 3100000 +5 halt 2 3100000 +5 halt 3 3110000 +5 halt 4 3110000 +5 halt 5 3100000 +5 halt 6 3110000 +5 halt 7 3100000 +5 halt 8 3100000 +5 halt 9 3100000 +5 halt 10 3110000 +5 halt 11 3110000 +5 halt 12 3110000 +5 halt 13 3110000 +5 halt 14 3100000 +5 halt 15 3100000 +5 halt 16 3100000 +5 halt 17 3100000 +5 halt 18 3120000 +5 halt 19 3100000 +5 halt 20 3090000 +5 halt 21 3100000 +5 halt 22 3090000 +5 halt 23 3100000 +5 halt 24 3100000 +5 halt 25 3100000 +5 halt 26 3110000 +5 halt 27 3100000 +5 halt 28 3100000 +5 halt 29 3110000 +5 halt 30 3100000 +# point pass=5 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=49998980 +5 run 1 3180000 +5 run 2 3180000 +5 run 3 3200000 +5 run 4 3190000 +5 run 5 3180000 +5 run 6 3190000 +5 run 7 3190000 +5 run 8 3180000 +5 run 9 3190000 +5 run 10 3190000 +5 run 11 3170000 +5 run 12 3190000 +5 run 13 3180000 +5 run 14 3190000 +5 run 15 3180000 +5 run 16 3180000 +5 run 17 3220000 +5 run 18 3190000 +5 run 19 3180000 +5 run 20 3190000 +5 run 21 3180000 +5 run 22 3180000 +5 run 23 3180000 +5 run 24 3180000 +5 run 25 3180000 +5 run 26 3180000 +5 run 27 3200000 +5 run 28 3180000 +5 run 29 3190000 +5 run 30 3190000 +# point pass=5 state=run jobs_per_s=6249878.2 updates_per_s=0.0 clock_hz=49999014 +6 halt 1 3120000 +6 halt 2 3100000 +6 halt 3 3100000 +6 halt 4 3100000 +6 halt 5 3110000 +6 halt 6 3110000 +6 halt 7 3100000 +6 halt 8 3110000 +6 halt 9 3100000 +6 halt 10 3110000 +6 halt 11 3100000 +6 halt 12 3110000 +6 halt 13 3100000 +6 halt 14 3110000 +6 halt 15 3110000 +6 halt 16 3100000 +6 halt 17 3100000 +6 halt 18 3100000 +6 halt 19 3110000 +6 halt 20 3100000 +6 halt 21 3110000 +6 halt 22 3110000 +6 halt 23 3120000 +6 halt 24 3120000 +6 halt 25 3110000 +6 halt 26 3110000 +6 halt 27 3100000 +6 halt 28 3110000 +6 halt 29 3100000 +6 halt 30 3100000 +# point pass=6 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=49999283 +6 run 1 3190000 +6 run 2 3180000 +6 run 3 3190000 +6 run 4 3190000 +6 run 5 3170000 +6 run 6 3190000 +6 run 7 3200000 +6 run 8 3180000 +6 run 9 3200000 +6 run 10 3190000 +6 run 11 3350000 +6 run 12 3190000 +6 run 13 3190000 +6 run 14 3190000 +6 run 15 3180000 +6 run 16 3550000 +6 run 17 3190000 +6 run 18 3190000 +6 run 19 3190000 +6 run 20 3180000 +6 run 21 3180000 +6 run 22 3210000 +6 run 23 3320000 +6 run 24 3190000 +6 run 25 3180000 +6 run 26 3180000 +6 run 27 3190000 +6 run 28 3190000 +6 run 29 3190000 +6 run 30 3210000 +# point pass=6 state=run jobs_per_s=6249906.7 updates_per_s=0.0 clock_hz=49999241 +# done SystemTime { tv_sec: 1793976903, tv_nsec: 28061803 } +# passes used 6 of 6 (dropped: []) +# run - halt = 80.0 +/- 2.6 mW (per-pass sd 6.3); 6/6 passes positive; 5 sample excursions beyond 6 MAD +# per-pass differences (mW): 80.0 80.0 70.0 90.0 80.0 80.0 +# board power while running 3187 mW; jobs/s 6249880.1; updates/s 0.0; measured PL clock 49.999 MHz +# pre-registered prediction 80.0 mW: measured/predicted = 1.00, 0.0 sigma from it diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/t0-recall.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/t0-recall.dat new file mode 100644 index 000000000..24b2086c3 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/t0-recall.dat @@ -0,0 +1,629 @@ +# class t0-recall cores 1 +# wai-quantum-fpga meter passes=10 settle=60s samples=30 null=false sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793970777, tv_nsec: 815511996 } +# pass state sample power_uW +1 halt 1 3140000 +1 halt 2 3150000 +1 halt 3 3140000 +1 halt 4 3140000 +1 halt 5 3140000 +1 halt 6 3140000 +1 halt 7 3140000 +1 halt 8 3150000 +1 halt 9 3140000 +1 halt 10 3130000 +1 halt 11 3140000 +1 halt 12 3130000 +1 halt 13 3140000 +1 halt 14 3140000 +1 halt 15 3130000 +1 halt 16 3190000 +1 halt 17 3150000 +1 halt 18 3140000 +1 halt 19 3150000 +1 halt 20 3140000 +1 halt 21 3140000 +1 halt 22 3140000 +1 halt 23 3130000 +1 halt 24 3140000 +1 halt 25 3150000 +1 halt 26 3150000 +1 halt 27 3130000 +1 halt 28 3150000 +1 halt 29 3140000 +1 halt 30 3140000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997681 +1 run 1 3290000 +1 run 2 3300000 +1 run 3 3300000 +1 run 4 3300000 +1 run 5 3320000 +1 run 6 3290000 +1 run 7 3300000 +1 run 8 3300000 +1 run 9 3300000 +1 run 10 3290000 +1 run 11 3290000 +1 run 12 3300000 +1 run 13 3290000 +1 run 14 3290000 +1 run 15 3320000 +1 run 16 3300000 +1 run 17 3300000 +1 run 18 3290000 +1 run 19 3290000 +1 run 20 3290000 +1 run 21 3280000 +1 run 22 3290000 +1 run 23 3300000 +1 run 24 3300000 +1 run 25 3290000 +1 run 26 3290000 +1 run 27 3300000 +1 run 28 3290000 +1 run 29 3290000 +1 run 30 3290000 +# point pass=1 state=run jobs_per_s=12499718.2 updates_per_s=0.0 clock_hz=99997723 +2 halt 1 3130000 +2 halt 2 3140000 +2 halt 3 3130000 +2 halt 4 3130000 +2 halt 5 3150000 +2 halt 6 3130000 +2 halt 7 3120000 +2 halt 8 3140000 +2 halt 9 3140000 +2 halt 10 3140000 +2 halt 11 3140000 +2 halt 12 3120000 +2 halt 13 3150000 +2 halt 14 3120000 +2 halt 15 3140000 +2 halt 16 3140000 +2 halt 17 3130000 +2 halt 18 3140000 +2 halt 19 3120000 +2 halt 20 3150000 +2 halt 21 3130000 +2 halt 22 3130000 +2 halt 23 3120000 +2 halt 24 3120000 +2 halt 25 3140000 +2 halt 26 3130000 +2 halt 27 3120000 +2 halt 28 3150000 +2 halt 29 3150000 +2 halt 30 3120000 +# point pass=2 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997759 +2 run 1 3290000 +2 run 2 3310000 +2 run 3 3300000 +2 run 4 3290000 +2 run 5 3300000 +2 run 6 3290000 +2 run 7 3300000 +2 run 8 3310000 +2 run 9 3290000 +2 run 10 3290000 +2 run 11 3300000 +2 run 12 3290000 +2 run 13 3300000 +2 run 14 3300000 +2 run 15 3300000 +2 run 16 3320000 +2 run 17 3300000 +2 run 18 3290000 +2 run 19 3300000 +2 run 20 3290000 +2 run 21 3290000 +2 run 22 3300000 +2 run 23 3310000 +2 run 24 3300000 +2 run 25 3330000 +2 run 26 3310000 +2 run 27 3300000 +2 run 28 3300000 +2 run 29 3310000 +2 run 30 3300000 +# point pass=2 state=run jobs_per_s=12499726.4 updates_per_s=0.0 clock_hz=99997789 +3 run 1 3290000 +3 run 2 3290000 +3 run 3 3290000 +3 run 4 3300000 +3 run 5 3310000 +3 run 6 3290000 +3 run 7 3290000 +3 run 8 3300000 +3 run 9 3290000 +3 run 10 3300000 +3 run 11 3290000 +3 run 12 3300000 +3 run 13 3310000 +3 run 14 3300000 +3 run 15 3300000 +3 run 16 3290000 +3 run 17 3290000 +3 run 18 3300000 +3 run 19 3290000 +3 run 20 3290000 +3 run 21 3290000 +3 run 22 3310000 +3 run 23 3300000 +3 run 24 3290000 +3 run 25 3290000 +3 run 26 3300000 +3 run 27 3300000 +3 run 28 3290000 +3 run 29 3290000 +3 run 30 3300000 +# point pass=3 state=run jobs_per_s=12499729.5 updates_per_s=0.0 clock_hz=99997814 +3 halt 1 3140000 +3 halt 2 3150000 +3 halt 3 3140000 +3 halt 4 3140000 +3 halt 5 3140000 +3 halt 6 3130000 +3 halt 7 3140000 +3 halt 8 3130000 +3 halt 9 3140000 +3 halt 10 3160000 +3 halt 11 3150000 +3 halt 12 3140000 +3 halt 13 3140000 +3 halt 14 3130000 +3 halt 15 3140000 +3 halt 16 3150000 +3 halt 17 3140000 +3 halt 18 3130000 +3 halt 19 3140000 +3 halt 20 3140000 +3 halt 21 3130000 +3 halt 22 3130000 +3 halt 23 3140000 +3 halt 24 3140000 +3 halt 25 3150000 +3 halt 26 3140000 +3 halt 27 3130000 +3 halt 28 3130000 +3 halt 29 3120000 +3 halt 30 3130000 +# point pass=3 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997837 +4 halt 1 3120000 +4 halt 2 3130000 +4 halt 3 3130000 +4 halt 4 3130000 +4 halt 5 3130000 +4 halt 6 3130000 +4 halt 7 3120000 +4 halt 8 3120000 +4 halt 9 3120000 +4 halt 10 3120000 +4 halt 11 3120000 +4 halt 12 3120000 +4 halt 13 3130000 +4 halt 14 3120000 +4 halt 15 3120000 +4 halt 16 3120000 +4 halt 17 3120000 +4 halt 18 3120000 +4 halt 19 3120000 +4 halt 20 3140000 +4 halt 21 3140000 +4 halt 22 3130000 +4 halt 23 3120000 +4 halt 24 3120000 +4 halt 25 3130000 +4 halt 26 3130000 +4 halt 27 3120000 +4 halt 28 3120000 +4 halt 29 3140000 +4 halt 30 3120000 +# point pass=4 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997854 +4 run 1 3330000 +4 run 2 3290000 +4 run 3 3290000 +4 run 4 3290000 +4 run 5 3290000 +4 run 6 3300000 +4 run 7 3330000 +4 run 8 3300000 +4 run 9 3310000 +4 run 10 3290000 +4 run 11 3290000 +4 run 12 3300000 +4 run 13 3300000 +4 run 14 3300000 +4 run 15 3290000 +4 run 16 3290000 +4 run 17 3320000 +4 run 18 3310000 +4 run 19 3300000 +4 run 20 3290000 +4 run 21 3300000 +4 run 22 3300000 +4 run 23 3300000 +4 run 24 3300000 +4 run 25 3290000 +4 run 26 3310000 +4 run 27 3290000 +4 run 28 3300000 +4 run 29 3290000 +4 run 30 3290000 +# point pass=4 state=run jobs_per_s=12499736.5 updates_per_s=0.0 clock_hz=99997868 +5 halt 1 3120000 +5 halt 2 3140000 +5 halt 3 3130000 +5 halt 4 3130000 +5 halt 5 3130000 +5 halt 6 3150000 +5 halt 7 3130000 +5 halt 8 3130000 +5 halt 9 3130000 +5 halt 10 3120000 +5 halt 11 3130000 +5 halt 12 3140000 +5 halt 13 3130000 +5 halt 14 3150000 +5 halt 15 3150000 +5 halt 16 3130000 +5 halt 17 3120000 +5 halt 18 3120000 +5 halt 19 3120000 +5 halt 20 3140000 +5 halt 21 3140000 +5 halt 22 3130000 +5 halt 23 3130000 +5 halt 24 3130000 +5 halt 25 3120000 +5 halt 26 3120000 +5 halt 27 3140000 +5 halt 28 3130000 +5 halt 29 3130000 +5 halt 30 3120000 +# point pass=5 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997880 +5 run 1 3290000 +5 run 2 3320000 +5 run 3 3300000 +5 run 4 3280000 +5 run 5 3290000 +5 run 6 3290000 +5 run 7 3290000 +5 run 8 3280000 +5 run 9 3280000 +5 run 10 3290000 +5 run 11 3300000 +5 run 12 3290000 +5 run 13 3290000 +5 run 14 3280000 +5 run 15 3300000 +5 run 16 3290000 +5 run 17 3290000 +5 run 18 3290000 +5 run 19 3290000 +5 run 20 3290000 +5 run 21 3290000 +5 run 22 3280000 +5 run 23 3300000 +5 run 24 3290000 +5 run 25 3290000 +5 run 26 3300000 +5 run 27 3290000 +5 run 28 3280000 +5 run 29 3280000 +5 run 30 3280000 +# point pass=5 state=run jobs_per_s=12499739.0 updates_per_s=0.0 clock_hz=99997890 +6 run 1 3300000 +6 run 2 3310000 +6 run 3 3290000 +6 run 4 3300000 +6 run 5 3300000 +6 run 6 3300000 +6 run 7 3310000 +6 run 8 3320000 +6 run 9 3290000 +6 run 10 3290000 +6 run 11 3290000 +6 run 12 3300000 +6 run 13 3290000 +6 run 14 3290000 +6 run 15 3310000 +6 run 16 3300000 +6 run 17 3310000 +6 run 18 3300000 +6 run 19 3320000 +6 run 20 3290000 +6 run 21 3290000 +6 run 22 3320000 +6 run 23 3310000 +6 run 24 3290000 +6 run 25 3300000 +6 run 26 3290000 +6 run 27 3710000 +6 run 28 3300000 +6 run 29 3300000 +6 run 30 3300000 +# point pass=6 state=run jobs_per_s=12499740.2 updates_per_s=0.0 clock_hz=99997899 +6 halt 1 3140000 +6 halt 2 3150000 +6 halt 3 3140000 +6 halt 4 3140000 +6 halt 5 3130000 +6 halt 6 3140000 +6 halt 7 3140000 +6 halt 8 3130000 +6 halt 9 3130000 +6 halt 10 3130000 +6 halt 11 3150000 +6 halt 12 3130000 +6 halt 13 3140000 +6 halt 14 3140000 +6 halt 15 3140000 +6 halt 16 3120000 +6 halt 17 3130000 +6 halt 18 3130000 +6 halt 19 3150000 +6 halt 20 3150000 +6 halt 21 3130000 +6 halt 22 3140000 +6 halt 23 3140000 +6 halt 24 3130000 +6 halt 25 3150000 +6 halt 26 3140000 +6 halt 27 3130000 +6 halt 28 3150000 +6 halt 29 3130000 +6 halt 30 3160000 +# point pass=6 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997906 +7 halt 1 3140000 +7 halt 2 3130000 +7 halt 3 3140000 +7 halt 4 3150000 +7 halt 5 3140000 +7 halt 6 3130000 +7 halt 7 3140000 +7 halt 8 3140000 +7 halt 9 3140000 +7 halt 10 3140000 +7 halt 11 3120000 +7 halt 12 3120000 +7 halt 13 3140000 +7 halt 14 3120000 +7 halt 15 3120000 +7 halt 16 3140000 +7 halt 17 3150000 +7 halt 18 3120000 +7 halt 19 3120000 +7 halt 20 3130000 +7 halt 21 3120000 +7 halt 22 3120000 +7 halt 23 3130000 +7 halt 24 3130000 +7 halt 25 3140000 +7 halt 26 3130000 +7 halt 27 3120000 +7 halt 28 3240000 +7 halt 29 3130000 +7 halt 30 3140000 +# point pass=7 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997913 +7 run 1 3300000 +7 run 2 3300000 +7 run 3 3300000 +7 run 4 3300000 +7 run 5 3310000 +7 run 6 3290000 +7 run 7 3300000 +7 run 8 3320000 +7 run 9 3300000 +7 run 10 3290000 +7 run 11 3300000 +7 run 12 3300000 +7 run 13 3300000 +7 run 14 3310000 +7 run 15 3300000 +7 run 16 3320000 +7 run 17 3310000 +7 run 18 3290000 +7 run 19 3310000 +7 run 20 3300000 +7 run 21 3290000 +7 run 22 3300000 +7 run 23 3300000 +7 run 24 3300000 +7 run 25 3300000 +7 run 26 3300000 +7 run 27 3300000 +7 run 28 3300000 +7 run 29 3300000 +7 run 30 3310000 +# point pass=7 state=run jobs_per_s=12499742.5 updates_per_s=0.0 clock_hz=99997918 +8 run 1 3320000 +8 run 2 3310000 +8 run 3 3640000 +8 run 4 3310000 +8 run 5 3330000 +8 run 6 3300000 +8 run 7 3300000 +8 run 8 3300000 +8 run 9 3310000 +8 run 10 3310000 +8 run 11 3300000 +8 run 12 3310000 +8 run 13 3320000 +8 run 14 3310000 +8 run 15 3310000 +8 run 16 3290000 +8 run 17 3300000 +8 run 18 3300000 +8 run 19 3310000 +8 run 20 3290000 +8 run 21 3290000 +8 run 22 3300000 +8 run 23 3290000 +8 run 24 3290000 +8 run 25 3290000 +8 run 26 3290000 +8 run 27 3300000 +8 run 28 3310000 +8 run 29 3300000 +8 run 30 3300000 +# point pass=8 state=run jobs_per_s=12499743.0 updates_per_s=0.0 clock_hz=99997922 +8 halt 1 3150000 +8 halt 2 3150000 +8 halt 3 3130000 +8 halt 4 3130000 +8 halt 5 3140000 +8 halt 6 3140000 +8 halt 7 3140000 +8 halt 8 3130000 +8 halt 9 3130000 +8 halt 10 3140000 +8 halt 11 3140000 +8 halt 12 3120000 +8 halt 13 3130000 +8 halt 14 3120000 +8 halt 15 3140000 +8 halt 16 3120000 +8 halt 17 3120000 +8 halt 18 3120000 +8 halt 19 3120000 +8 halt 20 3140000 +8 halt 21 3130000 +8 halt 22 3140000 +8 halt 23 3130000 +8 halt 24 3130000 +8 halt 25 3150000 +8 halt 26 3130000 +8 halt 27 3130000 +8 halt 28 3120000 +8 halt 29 3120000 +8 halt 30 3130000 +# point pass=8 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997926 +9 halt 1 3130000 +9 halt 2 3140000 +9 halt 3 3140000 +9 halt 4 3140000 +9 halt 5 3130000 +9 halt 6 3140000 +9 halt 7 3140000 +9 halt 8 3130000 +9 halt 9 3140000 +9 halt 10 3140000 +9 halt 11 3140000 +9 halt 12 3140000 +9 halt 13 3140000 +9 halt 14 3140000 +9 halt 15 3120000 +9 halt 16 3140000 +9 halt 17 3130000 +9 halt 18 3140000 +9 halt 19 3150000 +9 halt 20 3130000 +9 halt 21 3130000 +9 halt 22 3140000 +9 halt 23 3120000 +9 halt 24 3140000 +9 halt 25 3130000 +9 halt 26 3130000 +9 halt 27 3130000 +9 halt 28 3130000 +9 halt 29 3140000 +9 halt 30 3150000 +# point pass=9 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997929 +9 run 1 3290000 +9 run 2 3290000 +9 run 3 3310000 +9 run 4 3300000 +9 run 5 3290000 +9 run 6 3290000 +9 run 7 3290000 +9 run 8 3290000 +9 run 9 3290000 +9 run 10 3310000 +9 run 11 3300000 +9 run 12 3290000 +9 run 13 3290000 +9 run 14 3300000 +9 run 15 3290000 +9 run 16 3290000 +9 run 17 3300000 +9 run 18 3310000 +9 run 19 3320000 +9 run 20 3290000 +9 run 21 3290000 +9 run 22 3290000 +9 run 23 3290000 +9 run 24 3290000 +9 run 25 3290000 +9 run 26 3290000 +9 run 27 3320000 +9 run 28 3290000 +9 run 29 3300000 +9 run 30 3290000 +# point pass=9 state=run jobs_per_s=12499744.1 updates_per_s=0.0 clock_hz=99997931 +10 run 1 3300000 +10 run 2 3320000 +10 run 3 3290000 +10 run 4 3300000 +10 run 5 3300000 +10 run 6 3310000 +10 run 7 3300000 +10 run 8 3290000 +10 run 9 3300000 +10 run 10 3290000 +10 run 11 3300000 +10 run 12 3290000 +10 run 13 3290000 +10 run 14 3300000 +10 run 15 3290000 +10 run 16 3300000 +10 run 17 3300000 +10 run 18 3300000 +10 run 19 3320000 +10 run 20 3300000 +10 run 21 3300000 +10 run 22 3300000 +10 run 23 3300000 +10 run 24 3310000 +10 run 25 3310000 +10 run 26 3320000 +10 run 27 3300000 +10 run 28 3300000 +10 run 29 3310000 +10 run 30 3300000 +# point pass=10 state=run jobs_per_s=12499744.5 updates_per_s=0.0 clock_hz=99997934 +10 halt 1 3140000 +10 halt 2 3140000 +10 halt 3 3140000 +10 halt 4 3130000 +10 halt 5 3140000 +10 halt 6 3150000 +10 halt 7 3150000 +10 halt 8 3140000 +10 halt 9 3140000 +10 halt 10 3140000 +10 halt 11 3140000 +10 halt 12 3140000 +10 halt 13 3140000 +10 halt 14 3140000 +10 halt 15 3150000 +10 halt 16 3140000 +10 halt 17 3140000 +10 halt 18 3140000 +10 halt 19 3140000 +10 halt 20 3140000 +10 halt 21 3140000 +10 halt 22 3140000 +10 halt 23 3140000 +10 halt 24 3140000 +10 halt 25 3140000 +10 halt 26 3150000 +10 halt 27 3130000 +10 halt 28 3140000 +10 halt 29 3150000 +10 halt 30 3160000 +# point pass=10 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997935 +# done SystemTime { tv_sec: 1793972578, tv_nsec: 968782358 } +# passes used 10 of 10 (dropped: []) +# run - halt = 162.0 +/- 3.3 mW (per-pass sd 10.3); 10/10 passes positive; 4 sample excursions beyond 6 MAD +# per-pass differences (mW): 150.0 170.0 150.0 180.0 160.0 160.0 170.0 170.0 150.0 160.0 +# board power while running 3296 mW; jobs/s 12499736.4; updates/s 0.0; measured PL clock 99.998 MHz +# pre-registered prediction 160.0 mW: measured/predicted = 1.01, 0.6 sigma from it diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/first-attempt/known_answer.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/first-attempt/known_answer.dat new file mode 100644 index 000000000..3898f0bdc --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/first-attempt/known_answer.dat @@ -0,0 +1,483 @@ +# kv_runhalt bitstream=ev_rdet.bit.bin gpio=594 settle=60s passes=8 samples=30 +# pass vehicle sample power_uW +1 halt 1 3070000 +1 halt 2 3150000 +1 halt 3 3080000 +1 halt 4 3170000 +1 halt 5 3080000 +1 halt 6 3170000 +1 halt 7 3080000 +1 halt 8 3180000 +1 halt 9 3100000 +1 halt 10 3170000 +1 halt 11 3090000 +1 halt 12 3170000 +1 halt 13 3210000 +1 halt 14 3080000 +1 halt 15 3220000 +1 halt 16 3080000 +1 halt 17 3240000 +1 halt 18 3080000 +1 halt 19 3210000 +1 halt 20 3080000 +1 halt 21 3240000 +1 halt 22 3080000 +1 halt 23 3100000 +1 halt 24 3090000 +1 halt 25 3260000 +1 halt 26 3080000 +1 halt 27 3250000 +1 halt 28 3100000 +1 halt 29 3100000 +1 halt 30 3070000 +1 run 1 3170000 +1 run 2 3230000 +1 run 3 3150000 +1 run 4 3230000 +1 run 5 3150000 +1 run 6 3220000 +1 run 7 3150000 +1 run 8 3210000 +1 run 9 3150000 +1 run 10 3220000 +1 run 11 3150000 +1 run 12 3240000 +1 run 13 3150000 +1 run 14 3250000 +1 run 15 3150000 +1 run 16 3240000 +1 run 17 3150000 +1 run 18 3250000 +1 run 19 3150000 +1 run 20 3250000 +1 run 21 3150000 +1 run 22 3250000 +1 run 23 3150000 +1 run 24 3230000 +1 run 25 3150000 +1 run 26 3230000 +1 run 27 3150000 +1 run 28 3250000 +1 run 29 3150000 +1 run 30 3270000 +2 run 1 3170000 +2 run 2 3240000 +2 run 3 3160000 +2 run 4 3250000 +2 run 5 3160000 +2 run 6 3240000 +2 run 7 3170000 +2 run 8 3250000 +2 run 9 3160000 +2 run 10 3250000 +2 run 11 3150000 +2 run 12 3250000 +2 run 13 3160000 +2 run 14 3250000 +2 run 15 3160000 +2 run 16 3240000 +2 run 17 3160000 +2 run 18 3250000 +2 run 19 3160000 +2 run 20 3280000 +2 run 21 3180000 +2 run 22 3250000 +2 run 23 3160000 +2 run 24 3260000 +2 run 25 3160000 +2 run 26 3230000 +2 run 27 3160000 +2 run 28 3240000 +2 run 29 3150000 +2 run 30 3260000 +2 halt 1 3150000 +2 halt 2 3080000 +2 halt 3 3140000 +2 halt 4 3080000 +2 halt 5 3150000 +2 halt 6 3070000 +2 halt 7 3120000 +2 halt 8 3090000 +2 halt 9 3140000 +2 halt 10 3100000 +2 halt 11 3110000 +2 halt 12 3080000 +2 halt 13 3120000 +2 halt 14 3070000 +2 halt 15 3130000 +2 halt 16 3080000 +2 halt 17 3120000 +2 halt 18 3080000 +2 halt 19 3140000 +2 halt 20 3080000 +2 halt 21 3150000 +2 halt 22 3090000 +2 halt 23 3170000 +2 halt 24 3080000 +2 halt 25 3170000 +2 halt 26 3080000 +2 halt 27 3180000 +2 halt 28 3100000 +2 halt 29 3210000 +2 halt 30 3090000 +3 run 1 3150000 +3 run 2 3160000 +3 run 3 3150000 +3 run 4 3150000 +3 run 5 3150000 +3 run 6 3150000 +3 run 7 3160000 +3 run 8 3150000 +3 run 9 3150000 +3 run 10 3150000 +3 run 11 3150000 +3 run 12 3160000 +3 run 13 3150000 +3 run 14 3150000 +3 run 15 3160000 +3 run 16 3150000 +3 run 17 3150000 +3 run 18 3160000 +3 run 19 3150000 +3 run 20 3150000 +3 run 21 3150000 +3 run 22 3150000 +3 run 23 3170000 +3 run 24 3150000 +3 run 25 3160000 +3 run 26 3150000 +3 run 27 3150000 +3 run 28 3140000 +3 run 29 3180000 +3 run 30 3150000 +3 halt 1 3170000 +3 halt 2 3080000 +3 halt 3 3170000 +3 halt 4 3110000 +3 halt 5 3180000 +3 halt 6 3080000 +3 halt 7 3180000 +3 halt 8 3090000 +3 halt 9 3180000 +3 halt 10 3100000 +3 halt 11 3160000 +3 halt 12 3080000 +3 halt 13 3180000 +3 halt 14 3080000 +3 halt 15 3180000 +3 halt 16 3090000 +3 halt 17 3190000 +3 halt 18 3100000 +3 halt 19 3170000 +3 halt 20 3080000 +3 halt 21 3190000 +3 halt 22 3090000 +3 halt 23 3190000 +3 halt 24 3080000 +3 halt 25 3190000 +3 halt 26 3100000 +3 halt 27 3190000 +3 halt 28 3080000 +3 halt 29 3180000 +3 halt 30 3080000 +4 halt 1 3100000 +4 halt 2 3070000 +4 halt 3 3080000 +4 halt 4 3070000 +4 halt 5 3070000 +4 halt 6 3070000 +4 halt 7 3090000 +4 halt 8 3080000 +4 halt 9 3090000 +4 halt 10 3070000 +4 halt 11 3100000 +4 halt 12 3070000 +4 halt 13 3100000 +4 halt 14 3080000 +4 halt 15 3130000 +4 halt 16 3070000 +4 halt 17 3120000 +4 halt 18 3090000 +4 halt 19 3130000 +4 halt 20 3100000 +4 halt 21 3160000 +4 halt 22 3080000 +4 halt 23 3160000 +4 halt 24 3070000 +4 halt 25 3160000 +4 halt 26 3070000 +4 halt 27 3160000 +4 halt 28 3090000 +4 halt 29 3170000 +4 halt 30 3070000 +4 run 1 3150000 +4 run 2 3150000 +4 run 3 3160000 +4 run 4 3160000 +4 run 5 3150000 +4 run 6 3160000 +4 run 7 3150000 +4 run 8 3150000 +4 run 9 3150000 +4 run 10 3150000 +4 run 11 3150000 +4 run 12 3150000 +4 run 13 3150000 +4 run 14 3150000 +4 run 15 3150000 +4 run 16 3170000 +4 run 17 3160000 +4 run 18 3170000 +4 run 19 3160000 +4 run 20 3160000 +4 run 21 3140000 +4 run 22 3180000 +4 run 23 3150000 +4 run 24 3200000 +4 run 25 3150000 +4 run 26 3180000 +4 run 27 3150000 +4 run 28 3200000 +4 run 29 3140000 +4 run 30 3180000 +5 run 1 3240000 +5 run 2 3160000 +5 run 3 3240000 +5 run 4 3150000 +5 run 5 3230000 +5 run 6 3160000 +5 run 7 3260000 +5 run 8 3140000 +5 run 9 3220000 +5 run 10 3130000 +5 run 11 3240000 +5 run 12 3150000 +5 run 13 3230000 +5 run 14 3150000 +5 run 15 3250000 +5 run 16 3140000 +5 run 17 3230000 +5 run 18 3140000 +5 run 19 3220000 +5 run 20 3150000 +5 run 21 3220000 +5 run 22 3140000 +5 run 23 3140000 +5 run 24 3230000 +5 run 25 3160000 +5 run 26 3240000 +5 run 27 3140000 +5 run 28 3230000 +5 run 29 3140000 +5 run 30 3250000 +5 halt 1 3070000 +5 halt 2 3240000 +5 halt 3 3080000 +5 halt 4 3070000 +5 halt 5 3070000 +5 halt 6 3090000 +5 halt 7 3070000 +5 halt 8 3170000 +5 halt 9 3070000 +5 halt 10 3170000 +5 halt 11 3070000 +5 halt 12 3160000 +5 halt 13 3070000 +5 halt 14 3170000 +5 halt 15 3070000 +5 halt 16 3170000 +5 halt 17 3070000 +5 halt 18 3160000 +5 halt 19 3100000 +5 halt 20 3180000 +5 halt 21 3080000 +5 halt 22 3190000 +5 halt 23 3070000 +5 halt 24 3170000 +5 halt 25 3080000 +5 halt 26 3160000 +5 halt 27 3070000 +5 halt 28 3160000 +5 halt 29 3070000 +5 halt 30 3180000 +6 halt 1 3250000 +6 halt 2 3080000 +6 halt 3 3240000 +6 halt 4 3080000 +6 halt 5 3240000 +6 halt 6 3090000 +6 halt 7 3250000 +6 halt 8 3080000 +6 halt 9 3250000 +6 halt 10 3120000 +6 halt 11 3250000 +6 halt 12 3100000 +6 halt 13 3270000 +6 halt 14 3080000 +6 halt 15 3080000 +6 halt 16 3090000 +6 halt 17 3080000 +6 halt 18 3090000 +6 halt 19 3090000 +6 halt 20 3080000 +6 halt 21 3090000 +6 halt 22 3090000 +6 halt 23 3080000 +6 halt 24 3090000 +6 halt 25 3080000 +6 halt 26 3090000 +6 halt 27 3080000 +6 halt 28 3080000 +6 halt 29 3080000 +6 halt 30 3090000 +6 run 1 3160000 +6 run 2 3240000 +6 run 3 3150000 +6 run 4 3240000 +6 run 5 3140000 +6 run 6 3240000 +6 run 7 3250000 +6 run 8 3150000 +6 run 9 3250000 +6 run 10 3140000 +6 run 11 3250000 +6 run 12 3150000 +6 run 13 3150000 +6 run 14 3150000 +6 run 15 3160000 +6 run 16 3150000 +6 run 17 3150000 +6 run 18 3150000 +6 run 19 3150000 +6 run 20 3150000 +6 run 21 3170000 +6 run 22 3160000 +6 run 23 3160000 +6 run 24 3150000 +6 run 25 3140000 +6 run 26 3160000 +6 run 27 3140000 +6 run 28 3150000 +6 run 29 3150000 +6 run 30 3150000 +7 halt 1 3080000 +7 halt 2 3160000 +7 halt 3 3080000 +7 halt 4 3160000 +7 halt 5 3080000 +7 halt 6 3190000 +7 halt 7 3070000 +7 halt 8 3160000 +7 halt 9 3100000 +7 halt 10 3190000 +7 halt 11 3080000 +7 halt 12 3190000 +7 halt 13 3080000 +7 halt 14 3190000 +7 halt 15 3080000 +7 halt 16 3220000 +7 halt 17 3080000 +7 halt 18 3210000 +7 halt 19 3080000 +7 halt 20 3230000 +7 halt 21 3090000 +7 halt 22 3210000 +7 halt 23 3090000 +7 halt 24 3220000 +7 halt 25 3080000 +7 halt 26 3230000 +7 halt 27 3080000 +7 halt 28 3250000 +7 halt 29 3090000 +7 halt 30 3110000 +7 run 1 3190000 +7 run 2 3140000 +7 run 3 3170000 +7 run 4 3150000 +7 run 5 3190000 +7 run 6 3150000 +7 run 7 3180000 +7 run 8 3150000 +7 run 9 3180000 +7 run 10 3150000 +7 run 11 3190000 +7 run 12 3410000 +7 run 13 3190000 +7 run 14 3140000 +7 run 15 3190000 +7 run 16 3150000 +7 run 17 3220000 +7 run 18 3150000 +7 run 19 3230000 +7 run 20 3150000 +7 run 21 3210000 +7 run 22 3140000 +7 run 23 3220000 +7 run 24 3240000 +7 run 25 3150000 +7 run 26 3230000 +7 run 27 3140000 +7 run 28 3260000 +7 run 29 3150000 +7 run 30 3250000 +8 halt 1 3090000 +8 halt 2 3080000 +8 halt 3 3080000 +8 halt 4 3080000 +8 halt 5 3090000 +8 halt 6 3080000 +8 halt 7 3130000 +8 halt 8 3090000 +8 halt 9 3130000 +8 halt 10 3070000 +8 halt 11 3120000 +8 halt 12 3070000 +8 halt 13 3110000 +8 halt 14 3080000 +8 halt 15 3100000 +8 halt 16 3080000 +8 halt 17 3110000 +8 halt 18 3080000 +8 halt 19 3120000 +8 halt 20 3080000 +8 halt 21 3140000 +8 halt 22 3070000 +8 halt 23 3120000 +8 halt 24 3080000 +8 halt 25 3120000 +8 halt 26 3070000 +8 halt 27 3140000 +8 halt 28 3080000 +8 halt 29 3140000 +8 halt 30 3080000 +8 run 1 3290000 +8 run 2 3150000 +8 run 3 3270000 +8 run 4 3150000 +8 run 5 3260000 +8 run 6 3150000 +8 run 7 3290000 +8 run 8 3160000 +8 run 9 3270000 +8 run 10 3170000 +8 run 11 3280000 +8 run 12 3150000 +8 run 13 3300000 +8 run 14 3150000 +8 run 15 3290000 +8 run 16 3170000 +8 run 17 3320000 +8 run 18 3160000 +8 run 19 3270000 +8 run 20 3170000 +8 run 21 3270000 +8 run 22 3170000 +8 run 23 3280000 +8 run 24 3160000 +8 run 25 3280000 +8 run 26 3160000 +8 run 27 3410000 +8 run 28 3150000 +8 run 29 3170000 +8 run 30 3160000 +# done diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/first-attempt/step.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/first-attempt/step.dat new file mode 100644 index 000000000..122da6210 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/first-attempt/step.dat @@ -0,0 +1,610 @@ +# class t0-recall cores 1 +# wai-quantum-fpga meter passes=1 settle=0s samples=300 null=false sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793952101, tv_nsec: 820210424 } +# pass state sample power_uW +1 halt 1 3370000 +1 halt 2 3140000 +1 halt 3 3120000 +1 halt 4 3130000 +1 halt 5 3120000 +1 halt 6 3120000 +1 halt 7 3110000 +1 halt 8 3120000 +1 halt 9 3130000 +1 halt 10 3130000 +1 halt 11 3120000 +1 halt 12 3140000 +1 halt 13 3110000 +1 halt 14 3120000 +1 halt 15 3120000 +1 halt 16 3120000 +1 halt 17 3130000 +1 halt 18 3130000 +1 halt 19 3130000 +1 halt 20 3110000 +1 halt 21 3120000 +1 halt 22 3140000 +1 halt 23 3130000 +1 halt 24 3120000 +1 halt 25 3130000 +1 halt 26 3140000 +1 halt 27 3130000 +1 halt 28 3110000 +1 halt 29 3120000 +1 halt 30 3120000 +1 halt 31 3120000 +1 halt 32 3120000 +1 halt 33 3130000 +1 halt 34 3130000 +1 halt 35 3150000 +1 halt 36 3120000 +1 halt 37 3120000 +1 halt 38 3130000 +1 halt 39 3120000 +1 halt 40 3120000 +1 halt 41 3120000 +1 halt 42 3110000 +1 halt 43 3120000 +1 halt 44 3130000 +1 halt 45 3120000 +1 halt 46 3120000 +1 halt 47 3120000 +1 halt 48 3120000 +1 halt 49 3120000 +1 halt 50 3140000 +1 halt 51 3130000 +1 halt 52 3120000 +1 halt 53 3120000 +1 halt 54 3130000 +1 halt 55 3120000 +1 halt 56 3130000 +1 halt 57 3120000 +1 halt 58 3140000 +1 halt 59 3140000 +1 halt 60 3120000 +1 halt 61 3130000 +1 halt 62 3130000 +1 halt 63 3120000 +1 halt 64 3120000 +1 halt 65 3120000 +1 halt 66 3120000 +1 halt 67 3150000 +1 halt 68 3120000 +1 halt 69 3120000 +1 halt 70 3130000 +1 halt 71 3120000 +1 halt 72 3130000 +1 halt 73 3130000 +1 halt 74 3120000 +1 halt 75 3110000 +1 halt 76 3120000 +1 halt 77 3130000 +1 halt 78 3120000 +1 halt 79 3110000 +1 halt 80 3120000 +1 halt 81 3120000 +1 halt 82 3140000 +1 halt 83 3120000 +1 halt 84 3120000 +1 halt 85 3130000 +1 halt 86 3130000 +1 halt 87 3120000 +1 halt 88 3120000 +1 halt 89 3120000 +1 halt 90 3140000 +1 halt 91 3150000 +1 halt 92 3130000 +1 halt 93 3120000 +1 halt 94 3130000 +1 halt 95 3120000 +1 halt 96 3130000 +1 halt 97 3120000 +1 halt 98 3120000 +1 halt 99 3140000 +1 halt 100 3120000 +1 halt 101 3120000 +1 halt 102 3120000 +1 halt 103 3120000 +1 halt 104 3140000 +1 halt 105 3120000 +1 halt 106 3140000 +1 halt 107 3130000 +1 halt 108 3130000 +1 halt 109 3140000 +1 halt 110 3130000 +1 halt 111 3140000 +1 halt 112 3140000 +1 halt 113 3150000 +1 halt 114 3150000 +1 halt 115 3120000 +1 halt 116 3130000 +1 halt 117 3130000 +1 halt 118 3140000 +1 halt 119 3120000 +1 halt 120 3140000 +1 halt 121 3130000 +1 halt 122 3150000 +1 halt 123 3140000 +1 halt 124 3130000 +1 halt 125 3140000 +1 halt 126 3130000 +1 halt 127 3130000 +1 halt 128 3140000 +1 halt 129 3130000 +1 halt 130 3140000 +1 halt 131 3140000 +1 halt 132 3140000 +1 halt 133 3140000 +1 halt 134 3130000 +1 halt 135 3140000 +1 halt 136 3140000 +1 halt 137 3150000 +1 halt 138 3140000 +1 halt 139 3130000 +1 halt 140 3130000 +1 halt 141 3150000 +1 halt 142 3130000 +1 halt 143 3140000 +1 halt 144 3130000 +1 halt 145 3150000 +1 halt 146 3150000 +1 halt 147 3130000 +1 halt 148 3130000 +1 halt 149 3160000 +1 halt 150 3130000 +1 halt 151 3120000 +1 halt 152 3130000 +1 halt 153 3140000 +1 halt 154 3150000 +1 halt 155 3130000 +1 halt 156 3140000 +1 halt 157 3130000 +1 halt 158 3130000 +1 halt 159 3140000 +1 halt 160 3140000 +1 halt 161 3140000 +1 halt 162 3140000 +1 halt 163 3130000 +1 halt 164 3140000 +1 halt 165 3150000 +1 halt 166 3140000 +1 halt 167 3130000 +1 halt 168 3140000 +1 halt 169 3150000 +1 halt 170 3140000 +1 halt 171 3140000 +1 halt 172 3130000 +1 halt 173 3150000 +1 halt 174 3140000 +1 halt 175 3130000 +1 halt 176 3130000 +1 halt 177 3150000 +1 halt 178 3140000 +1 halt 179 3140000 +1 halt 180 3120000 +1 halt 181 3140000 +1 halt 182 3140000 +1 halt 183 3140000 +1 halt 184 3140000 +1 halt 185 3140000 +1 halt 186 3140000 +1 halt 187 3120000 +1 halt 188 3130000 +1 halt 189 3140000 +1 halt 190 3130000 +1 halt 191 3150000 +1 halt 192 3150000 +1 halt 193 3140000 +1 halt 194 3140000 +1 halt 195 3140000 +1 halt 196 3140000 +1 halt 197 3140000 +1 halt 198 3120000 +1 halt 199 3140000 +1 halt 200 3150000 +1 halt 201 3150000 +1 halt 202 3140000 +1 halt 203 3150000 +1 halt 204 3140000 +1 halt 205 3140000 +1 halt 206 3140000 +1 halt 207 3140000 +1 halt 208 3140000 +1 halt 209 3150000 +1 halt 210 3130000 +1 halt 211 3140000 +1 halt 212 3130000 +1 halt 213 3140000 +1 halt 214 3140000 +1 halt 215 3140000 +1 halt 216 3130000 +1 halt 217 3130000 +1 halt 218 3120000 +1 halt 219 3140000 +1 halt 220 3130000 +1 halt 221 3140000 +1 halt 222 3140000 +1 halt 223 3130000 +1 halt 224 3140000 +1 halt 225 3140000 +1 halt 226 3130000 +1 halt 227 3130000 +1 halt 228 3150000 +1 halt 229 3130000 +1 halt 230 3130000 +1 halt 231 3130000 +1 halt 232 3150000 +1 halt 233 3140000 +1 halt 234 3120000 +1 halt 235 3130000 +1 halt 236 3130000 +1 halt 237 3120000 +1 halt 238 3140000 +1 halt 239 3120000 +1 halt 240 3130000 +1 halt 241 3130000 +1 halt 242 3120000 +1 halt 243 3260000 +1 halt 244 3120000 +1 halt 245 3130000 +1 halt 246 3130000 +1 halt 247 3120000 +1 halt 248 3500000 +1 halt 249 3130000 +1 halt 250 3130000 +1 halt 251 3120000 +1 halt 252 3120000 +1 halt 253 3120000 +1 halt 254 3140000 +1 halt 255 3140000 +1 halt 256 3140000 +1 halt 257 3120000 +1 halt 258 3120000 +1 halt 259 3120000 +1 halt 260 3120000 +1 halt 261 3110000 +1 halt 262 3120000 +1 halt 263 3120000 +1 halt 264 3140000 +1 halt 265 3120000 +1 halt 266 3120000 +1 halt 267 3130000 +1 halt 268 3130000 +1 halt 269 3130000 +1 halt 270 3120000 +1 halt 271 3120000 +1 halt 272 3120000 +1 halt 273 3120000 +1 halt 274 3120000 +1 halt 275 3120000 +1 halt 276 3120000 +1 halt 277 3120000 +1 halt 278 3140000 +1 halt 279 3150000 +1 halt 280 3120000 +1 halt 281 3120000 +1 halt 282 3120000 +1 halt 283 3130000 +1 halt 284 3130000 +1 halt 285 3120000 +1 halt 286 3130000 +1 halt 287 3140000 +1 halt 288 3140000 +1 halt 289 3120000 +1 halt 290 3130000 +1 halt 291 3120000 +1 halt 292 3120000 +1 halt 293 3120000 +1 halt 294 3120000 +1 halt 295 3120000 +1 halt 296 3140000 +1 halt 297 3120000 +1 halt 298 3120000 +1 halt 299 3130000 +1 halt 300 3120000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997998 +1 run 1 3520000 +1 run 2 3310000 +1 run 3 3300000 +1 run 4 3280000 +1 run 5 3290000 +1 run 6 3290000 +1 run 7 3290000 +1 run 8 3290000 +1 run 9 3290000 +1 run 10 3300000 +1 run 11 3290000 +1 run 12 3290000 +1 run 13 3290000 +1 run 14 3290000 +1 run 15 3290000 +1 run 16 3280000 +1 run 17 3290000 +1 run 18 3290000 +1 run 19 3290000 +1 run 20 3280000 +1 run 21 3300000 +1 run 22 3290000 +1 run 23 3280000 +1 run 24 3290000 +1 run 25 3310000 +1 run 26 3290000 +1 run 27 3290000 +1 run 28 3290000 +1 run 29 3290000 +1 run 30 3290000 +1 run 31 3290000 +1 run 32 3290000 +1 run 33 3300000 +1 run 34 3300000 +1 run 35 3290000 +1 run 36 3290000 +1 run 37 3300000 +1 run 38 3290000 +1 run 39 3290000 +1 run 40 3280000 +1 run 41 3280000 +1 run 42 3310000 +1 run 43 3290000 +1 run 44 3280000 +1 run 45 3300000 +1 run 46 3290000 +1 run 47 3290000 +1 run 48 3290000 +1 run 49 3290000 +1 run 50 3290000 +1 run 51 3290000 +1 run 52 3290000 +1 run 53 3290000 +1 run 54 3290000 +1 run 55 3290000 +1 run 56 3290000 +1 run 57 3300000 +1 run 58 3290000 +1 run 59 3290000 +1 run 60 3290000 +1 run 61 3300000 +1 run 62 3290000 +1 run 63 3290000 +1 run 64 3290000 +1 run 65 3320000 +1 run 66 3300000 +1 run 67 3290000 +1 run 68 3290000 +1 run 69 3290000 +1 run 70 3290000 +1 run 71 3280000 +1 run 72 3290000 +1 run 73 3290000 +1 run 74 3310000 +1 run 75 3300000 +1 run 76 3290000 +1 run 77 3290000 +1 run 78 3290000 +1 run 79 3290000 +1 run 80 3290000 +1 run 81 3290000 +1 run 82 3290000 +1 run 83 3290000 +1 run 84 3290000 +1 run 85 3290000 +1 run 86 3290000 +1 run 87 3290000 +1 run 88 3290000 +1 run 89 3300000 +1 run 90 3300000 +1 run 91 3290000 +1 run 92 3290000 +1 run 93 3290000 +1 run 94 3290000 +1 run 95 3290000 +1 run 96 3290000 +1 run 97 3310000 +1 run 98 3290000 +1 run 99 3290000 +1 run 100 3290000 +1 run 101 3290000 +1 run 102 3280000 +1 run 103 3300000 +1 run 104 3290000 +1 run 105 3290000 +1 run 106 3290000 +1 run 107 3290000 +1 run 108 3290000 +1 run 109 3290000 +1 run 110 3290000 +1 run 111 3290000 +1 run 112 3300000 +1 run 113 3290000 +1 run 114 3290000 +1 run 115 3290000 +1 run 116 3290000 +1 run 117 3290000 +1 run 118 3290000 +1 run 119 3290000 +1 run 120 3310000 +1 run 121 3310000 +1 run 122 3300000 +1 run 123 3290000 +1 run 124 3290000 +1 run 125 3290000 +1 run 126 3290000 +1 run 127 3290000 +1 run 128 3290000 +1 run 129 3310000 +1 run 130 3290000 +1 run 131 3290000 +1 run 132 3300000 +1 run 133 3290000 +1 run 134 3290000 +1 run 135 3290000 +1 run 136 3290000 +1 run 137 3290000 +1 run 138 3290000 +1 run 139 3290000 +1 run 140 3300000 +1 run 141 3290000 +1 run 142 3290000 +1 run 143 3290000 +1 run 144 3310000 +1 run 145 3290000 +1 run 146 3300000 +1 run 147 3290000 +1 run 148 3300000 +1 run 149 3290000 +1 run 150 3290000 +1 run 151 3290000 +1 run 152 3310000 +1 run 153 3300000 +1 run 154 3300000 +1 run 155 3300000 +1 run 156 3280000 +1 run 157 3280000 +1 run 158 3290000 +1 run 159 3290000 +1 run 160 3290000 +1 run 161 3290000 +1 run 162 3290000 +1 run 163 3290000 +1 run 164 3290000 +1 run 165 3290000 +1 run 166 3290000 +1 run 167 3290000 +1 run 168 3300000 +1 run 169 3290000 +1 run 170 3290000 +1 run 171 3290000 +1 run 172 3290000 +1 run 173 3290000 +1 run 174 3300000 +1 run 175 3320000 +1 run 176 3320000 +1 run 177 3310000 +1 run 178 3290000 +1 run 179 3300000 +1 run 180 3300000 +1 run 181 3300000 +1 run 182 3300000 +1 run 183 3310000 +1 run 184 3320000 +1 run 185 3290000 +1 run 186 3300000 +1 run 187 3310000 +1 run 188 3300000 +1 run 189 3300000 +1 run 190 3310000 +1 run 191 3300000 +1 run 192 3300000 +1 run 193 3300000 +1 run 194 3300000 +1 run 195 3300000 +1 run 196 3290000 +1 run 197 3300000 +1 run 198 3300000 +1 run 199 3320000 +1 run 200 3300000 +1 run 201 3300000 +1 run 202 3300000 +1 run 203 3290000 +1 run 204 3300000 +1 run 205 3300000 +1 run 206 3300000 +1 run 207 3320000 +1 run 208 3320000 +1 run 209 3300000 +1 run 210 3300000 +1 run 211 3310000 +1 run 212 3300000 +1 run 213 3300000 +1 run 214 3300000 +1 run 215 3310000 +1 run 216 3320000 +1 run 217 3300000 +1 run 218 3300000 +1 run 219 3310000 +1 run 220 3300000 +1 run 221 3300000 +1 run 222 3300000 +1 run 223 3300000 +1 run 224 3300000 +1 run 225 3300000 +1 run 226 3300000 +1 run 227 3300000 +1 run 228 3290000 +1 run 229 3290000 +1 run 230 3300000 +1 run 231 3320000 +1 run 232 3290000 +1 run 233 3300000 +1 run 234 3310000 +1 run 235 3300000 +1 run 236 3290000 +1 run 237 3290000 +1 run 238 3290000 +1 run 239 3300000 +1 run 240 3290000 +1 run 241 3290000 +1 run 242 3290000 +1 run 243 3290000 +1 run 244 3290000 +1 run 245 3290000 +1 run 246 3300000 +1 run 247 3290000 +1 run 248 3300000 +1 run 249 3290000 +1 run 250 3290000 +1 run 251 3290000 +1 run 252 3290000 +1 run 253 3290000 +1 run 254 3320000 +1 run 255 3290000 +1 run 256 3300000 +1 run 257 3290000 +1 run 258 3290000 +1 run 259 3290000 +1 run 260 3290000 +1 run 261 3290000 +1 run 262 3310000 +1 run 263 3300000 +1 run 264 3310000 +1 run 265 3290000 +1 run 266 3290000 +1 run 267 3300000 +1 run 268 3290000 +1 run 269 3290000 +1 run 270 3290000 +1 run 271 3310000 +1 run 272 3280000 +1 run 273 3290000 +1 run 274 3300000 +1 run 275 3290000 +1 run 276 3290000 +1 run 277 3290000 +1 run 278 3280000 +1 run 279 3310000 +1 run 280 3290000 +1 run 281 3300000 +1 run 282 3290000 +1 run 283 3290000 +1 run 284 3720000 +1 run 285 3300000 +1 run 286 3300000 +1 run 287 3290000 +1 run 288 3290000 +1 run 289 3410000 +1 run 290 3290000 +1 run 291 3290000 +1 run 292 3290000 +1 run 293 3290000 +1 run 294 3310000 +1 run 295 3310000 +1 run 296 3280000 +1 run 297 3290000 +1 run 298 3300000 +1 run 299 3290000 +1 run 300 3290000 +# point pass=1 state=run jobs_per_s=12499745.5 updates_per_s=0.0 clock_hz=99997962 +# done SystemTime { tv_sec: 1793952702, tv_nsec: 236216766 } +# passes used 1 of 1 (dropped: []) +# run - halt = 160.0 +/- NaN mW (per-pass sd NaN); 1/1 passes positive; 6 sample excursions beyond 6 MAD +# per-pass differences (mW): 160.0 +# board power while running 3290 mW; jobs/s 12499745.5; updates/s 0.0; measured PL clock 99.998 MHz diff --git a/wai/wai-quantum-landscape-fpga/rtl/landscape_axi.v b/wai/wai-quantum-landscape-fpga/rtl/landscape_axi.v index 5cf8d57d3..f4328d9b9 100644 --- a/wai/wai-quantum-landscape-fpga/rtl/landscape_axi.v +++ b/wai/wai-quantum-landscape-fpga/rtl/landscape_axi.v @@ -2,15 +2,16 @@ // The engine's link to the processor: an AXI-Lite register port, a write-list DMA from DDR, and a // chain sequencer that runs a batch of chains in fabric. // -// AXI-Lite (from the processor's low-power-domain master, through a protocol converter): word -// address = byte address >> 2. Words 0x0000-0x7FFF are the engine's own map (src/regs.rs); +// AXI-Lite (from the processor's full-power-domain master, through a width and a protocol +// converter): word address = byte address >> 2. Words 0x0000-0x7FFF are the engine's own map (src/regs.rs); // 0x8000 and up are this wrapper's (src/axi.rs). Every access is answered within a few clocks, // whatever the engine, the DMA or the sequencer is doing: an access that never completes would // hold the processor's bus, and on a shared, remote board nobody is there to power-cycle it. So // an engine-map access while the DMA or the sequencer holds the engine's bus is not queued: a // write is dropped, a read returns 0xDEADBEEF, and either counts in DROPS, which the host checks. // -// Write-list DMA (an AXI4 read master into the processor's high-performance port): the host puts +// Write-list DMA (an AXI4 read master, through a width converter, into the processor's +// high-performance port): the host puts // a list of 64-bit entries in DDR, {16'b0, addr[15:0], data[31:0]}, and the wrapper applies each // as a register write, in order, one every two clocks (the engine needs a clock between a // coupling-memory clear and the next write). An entry for the chain RAM (0x9000-0x97FF) is @@ -22,7 +23,12 @@ // chain RAM (BOUNDARY 0-3, X0_LO, X0_HI) into the engine, starts the job, waits for it, and copies // STATE 0-3, STEPS and STATUS into the result RAM (0xA000 + 8k). The job's mode, bound, table and // workload are what the host set before. The anneal loop runs in fabric; the processor only fills -// the chain RAM and reads the results. +// the chain RAM and collects the results. +// +// Result write-back (an AXI4 write master on the same port): WB_CHAINS chains of the result RAM from +// WB_FIRST, 32 bytes a chain (its eight words), go to DDR at WB_LO/HI in bursts of 16 beats, one +// burst outstanding. The address must be on a 128-byte boundary and the chains must not cross a 4 KiB +// page, 128 chains a page. A write error is held in WB_ERR; every burst is still sent. // // EMIO: eight input lines carry a constant signature (0xA5) that the host reads through the // processor's GPIO controller before it touches an AXI address: a GPIO read cannot hang the bus, @@ -64,11 +70,28 @@ module landscape_axi ( input wire m_rlast, input wire m_rvalid, output wire m_rready, + // AXI4 write master (64-bit data) + output reg [48:0] m_awaddr = 49'd0, + output reg [7:0] m_awlen = 8'd0, + output wire [2:0] m_awsize, + output wire [1:0] m_awburst, + output wire [3:0] m_awcache, + output wire [2:0] m_awprot, + output reg m_awvalid = 1'b0, + input wire m_awready, + output reg [63:0] m_wdata = 64'd0, + output wire [7:0] m_wstrb, + output reg m_wlast = 1'b0, + output reg m_wvalid = 1'b0, + input wire m_wready, + input wire [1:0] m_bresp, + input wire m_bvalid, + output wire m_bready, // EMIO signature output wire [94:0] emio_i ); localparam [31:0] LAXI = 32'h4C41_5849; // "LAXI" - localparam [31:0] WRAP_VERSION = 32'h0000_0001; + localparam [31:0] WRAP_VERSION = 32'h0000_0002; assign s_bresp = 2'b00; assign s_rresp = 2'b00; assign emio_i = {87'd0, 8'hA5}; @@ -94,6 +117,15 @@ module landscape_axi ( reg [2:0] seq_i = 3'd0; reg [3:0] seq_wait = 4'd0; wire bus_free = !dma_busy && !seq_busy; + reg [31:0] wb_lo = 32'd0, wb_hi = 32'd0, wb_done_count = 32'd0; + reg [7:0] wb_first = 8'd0; + reg [8:0] wb_chains = 9'd0; + reg wb_busy = 1'b0, wb_err = 1'b0; + reg [2:0] wb_ph = 3'd0; // 0 address, 1-2 the two words of a beat, 3 data, 4 response + reg [10:0] wb_ra = 11'd0; // the result RAM word being fetched + reg [10:0] wb_beats = 11'd0; // beats still to send + reg [4:0] wb_left = 5'd0; // beats left in this burst + reg [31:0] wb_word = 32'd0; // ---- the chain RAM and the result RAM: 256 chains x 8 words each, one write port each ------- reg [31:0] chain_ram [0:2047]; @@ -109,7 +141,7 @@ module landscape_axi ( end always @(posedge clk) begin if (rr_we) res_ram[rr_wa] <= rr_wd; - res_q <= res_ram[ar_word[10:0]]; + res_q <= res_ram[wb_busy ? wb_ra : ar_word[10:0]]; end // ---- AXI-Lite capture --------------------------------------------------------------------- @@ -130,6 +162,14 @@ module landscape_axi ( reg [63:0] ent = 64'd0; assign m_rready = !ent_have; + // ---- the write-back's write master --------------------------------------------------------- + assign m_awsize = 3'd3; + assign m_awburst = 2'b01; + assign m_awcache = 4'b0011; + assign m_awprot = 3'b000; + assign m_wstrb = 8'hFF; + assign m_bready = 1'b1; + always @(posedge clk) begin stb <= 1'b0; we <= 1'b0; @@ -138,6 +178,7 @@ module landscape_axi ( s_awready <= 1'b0; s_wready <= 1'b0; s_arready <= 1'b0; if (!rstn) begin dma_busy <= 1'b0; seq_busy <= 1'b0; m_arvalid <= 1'b0; ent_have <= 1'b0; + wb_busy <= 1'b0; m_awvalid <= 1'b0; m_wvalid <= 1'b0; s_bvalid <= 1'b0; s_rvalid <= 1'b0; aw_have <= 1'b0; w_have <= 1'b0; ar_have <= 1'b0; host_ph <= 3'd0; end else begin // capture the host's access: an address and its data, or a read address @@ -208,6 +249,47 @@ module landscape_axi ( endcase end + // ---- the write-back: per burst an address, then per beat two words, then the response ---- + if (wb_busy) begin + case (wb_ph) + 3'd0: begin + if (!m_awvalid) begin + m_awvalid <= 1'b1; + m_awlen <= (wb_beats > 11'd16) ? 8'd15 : wb_beats[7:0] - 8'd1; + wb_left <= (wb_beats > 11'd16) ? 5'd16 : wb_beats[4:0]; + end else if (m_awready) begin + m_awvalid <= 1'b0; + m_awaddr <= m_awaddr + ({41'd0, m_awlen} + 49'd1) * 49'd8; + wb_ph <= 3'd1; + end + end + 3'd1: begin wb_ra <= wb_ra + 11'd1; wb_ph <= 3'd2; end // res_q follows wb_ra + 3'd2: begin wb_word <= res_q; wb_ph <= 3'd5; end + 3'd5: begin // the high word's turn + m_wdata <= {res_q, wb_word}; + m_wvalid <= 1'b1; + m_wlast <= (wb_left == 5'd1); + wb_ra <= wb_ra + 11'd1; + wb_ph <= 3'd3; + end + 3'd3: if (m_wready) begin + m_wvalid <= 1'b0; + wb_left <= wb_left - 5'd1; + wb_beats <= wb_beats - 11'd1; + wb_ph <= (wb_left == 5'd1) ? 3'd4 : 3'd1; + end + 3'd4: if (m_bvalid) begin + if (m_bresp != 2'b00) wb_err <= 1'b1; + if (wb_beats == 11'd0) begin + wb_busy <= 1'b0; + wb_done_count <= wb_done_count + 32'd1; + end + wb_ph <= 3'd0; + end + default: wb_ph <= 3'd0; + endcase + end + // ---- the host ---- if (aw_have && w_have && host_ph == 3'd0 && !s_bvalid && !ent_have) begin if (aw_word < 16'h8000) begin @@ -224,9 +306,19 @@ module landscape_axi ( end 16'h8008: drops <= 32'd0; 16'h8010: seq_count <= (w_val > 32'd256) ? 9'd256 : w_val[8:0]; - 16'h8011: if (bus_free && seq_count != 9'd0) begin + 16'h8011: if (bus_free && !wb_busy && seq_count != 9'd0) begin seq_busy <= 1'b1; seq_k <= 16'd0; seq_ph <= 4'd0; seq_i <= 3'd0; end + 16'h8020: wb_lo <= w_val; + 16'h8021: wb_hi <= w_val; + 16'h8022: wb_first <= w_val[7:0]; + 16'h8023: wb_chains <= (w_val > 32'd256) ? 9'd256 : w_val[8:0]; + 16'h8024: if (bus_free && !wb_busy && wb_chains != 9'd0) begin + wb_busy <= 1'b1; wb_err <= 1'b0; wb_ph <= 3'd0; + wb_ra <= {wb_first, 3'd0}; + wb_beats <= {wb_chains, 2'b00}; + m_awaddr <= {wb_hi[16:0], wb_lo}; + end 16'h8013: if (seq_busy) begin // abandon the batch and stop the engine's job seq_busy <= 1'b0; @@ -245,7 +337,7 @@ module landscape_axi ( end else begin case (ar_word) 16'h8000: wrap_rdata <= LAXI; - 16'h8001: wrap_rdata <= {29'd0, dma_err, dma_busy, seq_busy}; + 16'h8001: wrap_rdata <= {27'd0, wb_err, wb_busy, dma_err, dma_busy, seq_busy}; 16'h8002: wrap_rdata <= dma_lo; 16'h8003: wrap_rdata <= dma_hi; 16'h8004: wrap_rdata <= dma_count; @@ -255,6 +347,7 @@ module landscape_axi ( 16'h8009: wrap_rdata <= WRAP_VERSION; 16'h8010: wrap_rdata <= {23'd0, seq_count}; 16'h8012: wrap_rdata <= {16'd0, seq_done_count}; + 16'h8025: wrap_rdata <= wb_done_count; default: wrap_rdata <= 32'hDEAD_BEEF; endcase host_ph <= (ar_word >= 16'hA000 && ar_word < 16'hA800) ? 3'd5 : 3'd4; diff --git a/wai/wai-quantum-landscape-fpga/rtl/landscape_top_axi.v b/wai/wai-quantum-landscape-fpga/rtl/landscape_top_axi.v index ffb577ba5..97122a5b5 100644 --- a/wai/wai-quantum-landscape-fpga/rtl/landscape_top_axi.v +++ b/wai/wai-quantum-landscape-fpga/rtl/landscape_top_axi.v @@ -1,7 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // KV260 top for the AXI link: the processing system gives the clock, an AXI-Lite master (its -// low-power-domain port, through a protocol converter), a high-performance slave port into DDR, -// and the EMIO lines. The reset is the fabric's own: a counter that holds the link's control in +// full-power-domain master port, through a width and a protocol converter), a high-performance slave port +// into DDR (through a width converter), and the EMIO lines. See landscape.tcl for why the widths +// are what they are. The reset is the fabric's own: a counter that holds the link's control in // reset for 128 clocks after configuration, so the design does not depend on the state the // processor's PL reset line was left in. `timescale 1ns/1ps @@ -22,9 +23,13 @@ module design_top(); wire [1:0] d_arburst, d_rresp; wire [3:0] d_arcache; wire d_arvalid, d_arready, d_rlast, d_rvalid, d_rready; - wire [5:0] d_rid, d_bid; - wire [1:0] d_bresp; - wire d_awready, d_wready, d_bvalid; + wire [48:0] d_awaddr; + wire [7:0] d_awlen, d_wstrb; + wire [2:0] d_awsize, d_awprot; + wire [1:0] d_awburst, d_bresp; + wire [3:0] d_awcache; + wire [63:0] d_wdata; + wire d_awvalid, d_awready, d_wlast, d_wvalid, d_wready, d_bvalid, d_bready; wire [63:0] d_rdata; wire [94:0] emio_o, emio_i; sys_wrapper ps_i ( @@ -36,13 +41,12 @@ module design_top(); .LITE_rdata(l_rdata), .LITE_rresp(l_rresp), .LITE_rvalid(l_rvalid), .LITE_rready(l_rready), .DMA_araddr(d_araddr), .DMA_arlen(d_arlen), .DMA_arsize(d_arsize), .DMA_arburst(d_arburst), .DMA_arcache(d_arcache), .DMA_arprot(d_arprot), .DMA_arvalid(d_arvalid), .DMA_arready(d_arready), - .DMA_arlock(1'b0), .DMA_arqos(4'd0), .DMA_arid(6'd0), .DMA_aruser(1'b0), + .DMA_arlock(1'b0), .DMA_arqos(4'd0), .DMA_arregion(4'd0), .DMA_rdata(d_rdata), .DMA_rresp(d_rresp), .DMA_rlast(d_rlast), .DMA_rvalid(d_rvalid), .DMA_rready(d_rready), - .DMA_rid(d_rid), - .DMA_awaddr(49'd0), .DMA_awlen(8'd0), .DMA_awsize(3'd3), .DMA_awburst(2'b01), .DMA_awlock(1'b0), - .DMA_awcache(4'd0), .DMA_awprot(3'd0), .DMA_awqos(4'd0), .DMA_awvalid(1'b0), .DMA_awid(6'd0), .DMA_awuser(1'b0), - .DMA_awready(d_awready), .DMA_wdata(64'd0), .DMA_wstrb(8'd0), .DMA_wlast(1'b0), .DMA_wvalid(1'b0), .DMA_wready(d_wready), - .DMA_bid(d_bid), .DMA_bresp(d_bresp), .DMA_bvalid(d_bvalid), .DMA_bready(1'b1) + .DMA_awaddr(d_awaddr), .DMA_awlen(d_awlen), .DMA_awsize(d_awsize), .DMA_awburst(d_awburst), .DMA_awcache(d_awcache), + .DMA_awprot(d_awprot), .DMA_awvalid(d_awvalid), .DMA_awready(d_awready), .DMA_awlock(1'b0), .DMA_awqos(4'd0), .DMA_awregion(4'd0), + .DMA_wdata(d_wdata), .DMA_wstrb(d_wstrb), .DMA_wlast(d_wlast), .DMA_wvalid(d_wvalid), .DMA_wready(d_wready), + .DMA_bresp(d_bresp), .DMA_bvalid(d_bvalid), .DMA_bready(d_bready) ); landscape_axi u ( .clk(pl_clk0), .rstn(rstn), @@ -53,6 +57,9 @@ module design_top(); .m_araddr(d_araddr), .m_arlen(d_arlen), .m_arsize(d_arsize), .m_arburst(d_arburst), .m_arcache(d_arcache), .m_arprot(d_arprot), .m_arvalid(d_arvalid), .m_arready(d_arready), .m_rdata(d_rdata), .m_rresp(d_rresp), .m_rlast(d_rlast), .m_rvalid(d_rvalid), .m_rready(d_rready), + .m_awaddr(d_awaddr), .m_awlen(d_awlen), .m_awsize(d_awsize), .m_awburst(d_awburst), .m_awcache(d_awcache), + .m_awprot(d_awprot), .m_awvalid(d_awvalid), .m_awready(d_awready), .m_wdata(d_wdata), .m_wstrb(d_wstrb), + .m_wlast(d_wlast), .m_wvalid(d_wvalid), .m_wready(d_wready), .m_bresp(d_bresp), .m_bvalid(d_bvalid), .m_bready(d_bready), .emio_i(emio_i) ); endmodule diff --git a/wai/wai-quantum-landscape-fpga/src/axi.rs b/wai/wai-quantum-landscape-fpga/src/axi.rs index d526e39b0..e08bbd80e 100644 --- a/wai/wai-quantum-landscape-fpga/src/axi.rs +++ b/wai/wai-quantum-landscape-fpga/src/axi.rs @@ -1,6 +1,6 @@ //! The board's AXI link (`rtl/landscape_axi.v`): the engine's registers on an AXI-Lite port at -//! 0x8000_0000, a write-list DMA that reads bulk loads from DDR, and a sequencer that runs a batch -//! of chains in fabric. +//! 0xA000_0000, a write-list DMA that reads bulk loads from DDR, a sequencer that runs a batch of +//! chains in fabric, and a write-back that puts a batch's results in DDR. //! //! An AXI access to a programmable-logic address that nothing answers never completes, and it holds //! the processor until the board is power-cycled. This board is shared and remote, and another @@ -14,7 +14,7 @@ /// The wrapper's registers and RAMs, as word addresses on the link. pub const WRAP_ID: u16 = 0x8000; pub const WRAP_ID_VALUE: u32 = 0x4C41_5849; // "LAXI" -pub const WRAP_STATUS: u16 = 0x8001; // [0] sequencer busy [1] DMA busy [2] DMA error +pub const WRAP_STATUS: u16 = 0x8001; // [0] sequencer busy [1] DMA busy [2] DMA error [3] write-back busy [4] write-back error pub const DMA_LO: u16 = 0x8002; pub const DMA_HI: u16 = 0x8003; pub const DMA_COUNT: u16 = 0x8004; @@ -23,11 +23,17 @@ pub const DMA_DONE: u16 = 0x8006; pub const DMA_LEFT: u16 = 0x8007; pub const WRAP_DROPS: u16 = 0x8008; pub const WRAP_VERSION: u16 = 0x8009; -pub const WRAP_VERSION_VALUE: u32 = 1; +pub const WRAP_VERSION_VALUE: u32 = 2; pub const SEQ_COUNT: u16 = 0x8010; pub const SEQ_GO: u16 = 0x8011; pub const SEQ_DONE: u16 = 0x8012; pub const SEQ_ABORT: u16 = 0x8013; +pub const WB_LO: u16 = 0x8020; +pub const WB_HI: u16 = 0x8021; +pub const WB_FIRST: u16 = 0x8022; +pub const WB_CHAINS: u16 = 0x8023; +pub const WB_GO: u16 = 0x8024; +pub const WB_DONE: u16 = 0x8025; pub const CHAIN_RAM: u16 = 0x9000; pub const RESULT_RAM: u16 = 0xA000; /// Chains the chain RAM holds. @@ -35,12 +41,16 @@ pub const SEQ_MAX: usize = 256; /// The EMIO signature: bank 3, bits 7:0. pub const SIGNATURE: u32 = 0xA5; /// The link's physical address and size. -pub const LITE_BASE: u64 = 0x8000_0000; +pub const LITE_BASE: u64 = 0xA000_0000; pub const LITE_LEN: usize = 0x4_0000; pub const ST_SEQ: u32 = 1; pub const ST_DMA: u32 = 2; pub const ST_DMA_ERR: u32 = 4; +pub const ST_WB: u32 = 8; +pub const ST_WB_ERR: u32 = 16; +/// Bytes a chain takes in the result RAM and in a write-back: eight words. +pub const WB_CHAIN_BYTES: usize = 32; #[cfg(unix)] pub use board_port::*; @@ -94,6 +104,23 @@ mod board_port { #[cfg(not(target_arch = "aarch64"))] fn clean(_p: *const u8, _len: usize) {} + /// Cleans and invalidates `len` bytes from `p`: before a write-back, so nothing in the caches + /// lands on what the link writes, and after it, so the processor reads DDR and not a line it + /// fetched meanwhile. + #[cfg(target_arch = "aarch64")] + fn flush(p: *const u8, len: usize) { + let line = dline(); + let mut a = p as usize & !(line - 1); + let end = p as usize + len; + while a < end { + unsafe { core::arch::asm!("dc civac, {0}", in(reg) a) }; + a += line; + } + unsafe { core::arch::asm!("dsb sy") }; + } + #[cfg(not(target_arch = "aarch64"))] + fn flush(_p: *const u8, _len: usize) {} + /// Pinned pages for write lists, and their physical addresses from /proc/self/pagemap (root). pub struct Dma { buf: *mut u8, @@ -139,6 +166,33 @@ mod board_port { Ok(pfn * self.page as u64) } + /// Chains a page's write-back holds. + pub fn chains_per_page(&self) -> usize { + self.page / WB_CHAIN_BYTES + } + + /// The pages for lists; the last two are for write-backs. + fn list_pages(&self) -> usize { + self.pages - 2 + } + + fn result_slot(&self, j: usize) -> usize { + self.pages - 1 - j % 2 + } + + fn flush_page(&self, k: usize) { + flush(unsafe { self.buf.add(k * self.page) }, self.page); + } + + fn result(&self, k: usize, chain: usize) -> ResultWords { + let p = unsafe { self.buf.add(k * self.page + chain * WB_CHAIN_BYTES) } as *const u32; + let mut r = [0u32; 6]; + for (i, w) in r.iter_mut().enumerate() { + *w = unsafe { std::ptr::read_volatile(p.add(i)) }; + } + r + } + fn fill(&self, k: usize, list: &[(u16, u32)]) { let p = unsafe { self.buf.add(k * self.page) } as *mut u64; for (i, &(a, v)) in list.iter().enumerate() { @@ -162,22 +216,23 @@ mod board_port { } /// The processing system's settings for the two ports the link uses, as the board's boot left - /// them (read only): the low-power-domain master's data width (LPD_SLCR AFI_FS, bits 9:8; 0 is - /// 32 bits, which the link's AXI-Lite port is built for) and the high-performance port's read - /// and write widths (AFIFM2 RDCTRL and WRCTRL, bits 1:0; 1 is 64 bits, which the DMA is built - /// for). A width other than the build's would put data on the wrong byte lanes. - pub fn ps_ports() -> Result<(u32, u32, u32, String), Error> { - let lpd = Mem::map(0xFF41_9000, 0x1000)?.rd(0); + /// them (read only; loading a bitstream through the FPGA manager does not change them): the + /// full-power-domain master's data width (FPD_SLCR AFI_FS, bits 9:8; 2 is 128 bits) and the + /// high-performance port's read and write widths (AFIFM2 RDCTRL and WRCTRL, bits 1:0; 0 is 128 + /// bits). The AXI build is made for 128 bits on both, and a different width would put data on + /// the wrong byte lanes. Returns whether they match, and the sentence that says so. + pub fn ps_ports() -> Result<(bool, String), Error> { + let fpd = Mem::map(0xFD61_5000, 0x1000)?.rd(0); let afi = Mem::map(0xFD38_0000, 0x1000)?; let (rd, wr) = (afi.rd(0x000), afi.rd(0x014)); let w = |v: u32, map: [&'static str; 4]| map[(v & 3) as usize]; let text = format!( - "LPD_SLCR AFI_FS {lpd:#010x}: M_AXI_HPM0_LPD {} bits (the link wants 32); AFIFM2 RDCTRL {rd:#010x} WRCTRL {wr:#010x}: S_AXI_HP0_FPD reads {} bits, writes {} bits (the DMA wants 64 for reads)", - w(lpd >> 8, ["32", "64", "128", "?"]), + "FPD_SLCR AFI_FS {fpd:#010x}: M_AXI_HPM0_FPD {} bits; AFIFM2 RDCTRL {rd:#010x} WRCTRL {wr:#010x}: S_AXI_HP0_FPD reads {} bits, writes {} bits (the AXI build is made for 128 on the master and for reads)", + w(fpd >> 8, ["32", "64", "128", "?"]), w(rd, ["128", "64", "32", "?"]), w(wr, ["128", "64", "32", "?"]) ); - Ok((lpd, rd, wr, text)) + Ok(((fpd >> 8) & 3 == 2 && rd & 3 == 0, text)) } pub struct AxiPort { @@ -193,6 +248,7 @@ mod board_port { pub accesses: u64, pub dma_lists: u64, pub batches: u64, + pub write_backs: u64, } impl AxiPort { @@ -206,12 +262,12 @@ mod board_port { so no AXI address is touched. Load the AXI build first; if it was loaded, another session has replaced it." )); } - let (lpd, rd, _, text) = ps_ports()?; - if (lpd >> 8) & 3 != 0 { + let (ok, text) = ps_ports()?; + if !ok { return err(format!("the processing system's ports are not set as the AXI build needs, so no AXI address is touched: {text}")); } let lite = Mem::map(LITE_BASE, LITE_LEN)?; - let mut p = AxiPort { lite, gpio, guard: true, dma: None, dma_min: 32, sequencer: true, accesses: 0, dma_lists: 0, batches: 0 }; + let mut p = AxiPort { lite, gpio, guard: true, dma: None, dma_min: 32, sequencer: true, accesses: 0, dma_lists: 0, batches: 0, write_backs: 0 }; let id = p.rd(WRAP_ID)?; if id != WRAP_ID_VALUE { return err(format!("the AXI link answers {id:#010x} for its ID, want {WRAP_ID_VALUE:#010x}")); @@ -225,10 +281,7 @@ mod board_port { return err(format!("the AXI link is busy at open (status {st:#x}): another process is using it")); } p.wr(WRAP_DROPS, 0)?; - // the DMA only where the port reads 64 bits a beat, as the link is built for - if rd & 3 == 1 { - p.dma = Some(Dma::new(16)?); - } + p.dma = Some(Dma::new(16)?); Ok(p) } @@ -318,7 +371,7 @@ mod board_port { /// A write list of any length, a page at a time. pub fn dma_list(&mut self, list: &[(u16, u32)], timeout: Duration) -> Result<(), Error> { let (per, pages) = match &self.dma { - Some(d) => (d.per_page(), d.pages), + Some(d) => (d.per_page(), d.list_pages()), None => return bulk_plain(self, list), }; for (k, chunk) in list.chunks(per).enumerate() { @@ -327,6 +380,40 @@ mod board_port { Ok(()) } + /// The first `chains` results of the result RAM, written back to DDR a page at a time. + pub fn write_back(&mut self, chains: usize, timeout: Duration) -> Result, Error> { + let per = self.dma.as_ref().ok_or_else(|| Error("no DMA pages".into()))?.chains_per_page(); + let mut out = Vec::with_capacity(chains); + for (j, first) in (0..chains).step_by(per).enumerate() { + let n = per.min(chains - first); + let (slot, pa) = { + let d = self.dma.as_ref().ok_or_else(|| Error("no DMA pages".into()))?; + let slot = d.result_slot(j); + d.flush_page(slot); + (slot, d.phys(slot)?) + }; + let before = self.rd(WB_DONE)?; + self.wr(WB_LO, pa as u32)?; + self.wr(WB_HI, (pa >> 32) as u32)?; + self.wr(WB_FIRST, first as u32)?; + self.wr(WB_CHAINS, n as u32)?; + self.wr(WB_GO, 1)?; + let st = self.wait_wrap(ST_WB, timeout, "the write-back")?; + if st & ST_WB_ERR != 0 { + return err(format!("the write-back to {pa:#x} came back with an error")); + } + let after = self.rd(WB_DONE)?; + if after != before.wrapping_add(1) { + return err(format!("the write-back did not run (done count {before} -> {after})")); + } + let d = self.dma.as_ref().ok_or_else(|| Error("no DMA pages".into()))?; + d.flush_page(slot); + out.extend((0..n).map(|k| d.result(slot, k))); + self.write_backs += 1; + } + Ok(out) + } + fn drops_zero(&mut self) -> Result<(), Error> { let d = self.rd(WRAP_DROPS)?; if d != 0 { @@ -399,12 +486,16 @@ mod board_port { if after.wrapping_sub(before) & 0xFFFF != group.len() as u32 { return err(format!("the sequencer ran {} chains of {}", after.wrapping_sub(before) & 0xFFFF, group.len())); } - for k in 0..group.len() { - let mut r = [0u32; 6]; - for (i, w) in r.iter_mut().enumerate() { - *w = self.rd(RESULT_RAM + (8 * k + i) as u16)?; + if self.dma.is_some() { + out.extend(self.write_back(group.len(), timeout)?); + } else { + for k in 0..group.len() { + let mut r = [0u32; 6]; + for (i, w) in r.iter_mut().enumerate() { + *w = self.rd(RESULT_RAM + (8 * k + i) as u16)?; + } + out.push(r); } - out.push(r); } self.drops_zero()?; self.batches += 1; diff --git a/wai/wai-quantum-landscape-fpga/src/latency.rs b/wai/wai-quantum-landscape-fpga/src/latency.rs index 09608929d..6a7d87813 100644 --- a/wai/wai-quantum-landscape-fpga/src/latency.rs +++ b/wai/wai-quantum-landscape-fpga/src/latency.rs @@ -36,6 +36,16 @@ pub fn tick_hz() -> f64 { 1e9 } +/// Waits for every access before it to complete, the response included: a store alone is timed +/// only to its issue. +#[inline(always)] +pub fn dsb() { + #[cfg(target_arch = "aarch64")] + unsafe { + core::arch::asm!("dsb sy") + }; +} + /// A distribution of times, in nanoseconds. pub struct Dist(pub Vec); @@ -150,10 +160,26 @@ pub fn axi(p: &mut crate::axi::AxiPort, n: usize) -> String { }) .line("AXI-Lite read, an engine register")); s.push('\n'); - s.push_str(&time_each(n, || p.wr_raw(DMA_COUNT, 0)).line("AXI-Lite write, a link register")); + s.push_str(&time_each(n, dsb).line("a DSB alone")); s.push('\n'); - s.push_str(&time_each(n, || p.wr_raw(SEL, 0)).line("AXI-Lite write, an engine register")); + s.push_str(&time_each(n, || { + p.wr_raw(DMA_COUNT, 0); + dsb(); + }) + .line("AXI-Lite write and its response (DSB), a link register")); s.push('\n'); + s.push_str(&time_each(n, || { + p.wr_raw(SEL, 0); + dsb(); + }) + .line("AXI-Lite write and its response (DSB), an engine register")); + s.push('\n'); + let t = std::time::Instant::now(); + for _ in 0..n { + p.wr_raw(SEL, 0); + } + dsb(); + let _ = writeln!(s, " AXI-Lite writes back to back, no DSB between: {:.0} ns each over {n}", t.elapsed().as_secs_f64() * 1e9 / n as f64); s.push_str(&time_each(n, || { p.wr_raw(SEL, 0); std::hint::black_box(p.rd_raw(FIELD)); @@ -178,6 +204,11 @@ pub fn axi(p: &mut crate::axi::AxiPort, n: usize) -> String { let med = d.q(0.5); s.push_str(&d.line("a 512-entry write list by DMA (one page), kick to done")); let _ = writeln!(s, "\n = {:.1} ns an entry, {:.1} M register writes/s", med / 512.0, 512.0 / med * 1e3); + let d = time_each(n / 50, || { + p.write_back(128, std::time::Duration::from_secs(1)).expect("write-back"); + }); + s.push_str(&d.line("128 chains' results written back (one page), kick to read")); + let _ = writeln!(s, "\n = {:.0} ns a chain", d.q(0.5) / 128.0); s } diff --git a/wai/wai-quantum-landscape-fpga/src/main.rs b/wai/wai-quantum-landscape-fpga/src/main.rs index 52a8d5f1e..8fd42c171 100644 --- a/wai/wai-quantum-landscape-fpga/src/main.rs +++ b/wai/wai-quantum-landscape-fpga/src/main.rs @@ -331,8 +331,8 @@ fn main() { } #[cfg(unix)] "ps-ports" => { - let (_, _, _, text) = wai_quantum_landscape_fpga::axi::ps_ports().unwrap_or_else(|e| die(e)); - println!("{text}"); + let (ok, text) = wai_quantum_landscape_fpga::axi::ps_ports().unwrap_or_else(|e| die(e)); + println!("{text}: {}", if ok { "they match" } else { "they do not match" }); } #[cfg(unix)] "release" => { @@ -392,7 +392,7 @@ fn main() { e.port.sequencer = false; e.port.dma_min = usize::MAX; print!("{}", latency::bulk_paths(&mut e, "AXI, plain register accesses", 16_384).unwrap_or_else(|x| die(x))); - println!(" AXI accesses {}, DMA lists {}, sequencer batches {}", e.port.accesses, e.port.dma_lists, e.port.batches); + println!(" AXI accesses {}, DMA lists {}, sequencer batches {}, write-backs {}", e.port.accesses, e.port.dma_lists, e.port.batches, e.port.write_backs); } Link::Emio(mut p) => { print!("{}", latency::emio(&mut p, n / 4)); diff --git a/wai/wai-quantum-landscape-fpga/src/script.rs b/wai/wai-quantum-landscape-fpga/src/script.rs index 018546b2b..d9df51a21 100644 --- a/wai/wai-quantum-landscape-fpga/src/script.rs +++ b/wai/wai-quantum-landscape-fpga/src/script.rs @@ -12,8 +12,10 @@ //! and run the DMA over it //! Q n run the sequencer over n chains (their words already in the chain RAM) //! and wait for it +//! K n write the batch's n results back to memory; the next 6n lines are the +//! words each must hold //! -//! and a batch's results are checked as reads of the result RAM. +//! and a batch's results are also checked as reads of the result RAM. use crate::axi::{CHAIN_RAM, RESULT_RAM, WRAP_DROPS}; use crate::model::ModelPort; @@ -98,6 +100,13 @@ impl Port for ScriptPort { } } self.expect(WRAP_DROPS, 0, 0xFFFF_FFFF); + let _ = writeln!(self.text, "K {}", group.len()); + for r in &res { + for &w in r { + let _ = writeln!(self.text, "{w:08x}"); + } + self.reads += r.len(); + } out.extend(res); } Ok(out) diff --git a/wai/wai-quantum-landscape-fpga/tb/tb_landscape_axi.v b/wai/wai-quantum-landscape-fpga/tb/tb_landscape_axi.v index bdaa2115d..ca395091c 100644 --- a/wai/wai-quantum-landscape-fpga/tb/tb_landscape_axi.v +++ b/wai/wai-quantum-landscape-fpga/tb/tb_landscape_axi.v @@ -3,7 +3,8 @@ // against the AXI link (rtl/landscape_axi.v) and the engine behind it: register accesses over // AXI-Lite, write lists through the DMA from a memory model that answers with irregular delays and // several bursts outstanding, and batches of chains through the sequencer, whose results are read -// from the result RAM. Every read must return what the model returned. A mismatch names the line. +// from the result RAM and written back to the memory model. Every read and every word written back +// must be what the model returned. A mismatch names the line. `timescale 1ns/1ps module tb; reg clk = 0; @@ -26,6 +27,16 @@ module tb; reg m_arready = 0, m_rvalid = 0, m_rlast = 0; reg [63:0] m_rdata = 0; reg [1:0] m_rresp = 0; + // DMA write port + wire [48:0] m_awaddr; + wire [7:0] m_awlen, m_wstrb; + wire [2:0] m_awsize, m_awprot; + wire [1:0] m_awburst; + wire [3:0] m_awcache; + wire [63:0] m_wdata; + wire m_awvalid, m_wlast, m_wvalid, m_bready; + reg m_awready = 0, m_wready = 0, m_bvalid = 0; + reg [1:0] m_bresp = 0; wire [94:0] emio_i; landscape_axi dut ( @@ -37,6 +48,9 @@ module tb; .m_araddr(m_araddr), .m_arlen(m_arlen), .m_arsize(m_arsize), .m_arburst(m_arburst), .m_arcache(m_arcache), .m_arprot(m_arprot), .m_arvalid(m_arvalid), .m_arready(m_arready), .m_rdata(m_rdata), .m_rresp(m_rresp), .m_rlast(m_rlast), .m_rvalid(m_rvalid), .m_rready(m_rready), + .m_awaddr(m_awaddr), .m_awlen(m_awlen), .m_awsize(m_awsize), .m_awburst(m_awburst), .m_awcache(m_awcache), + .m_awprot(m_awprot), .m_awvalid(m_awvalid), .m_awready(m_awready), .m_wdata(m_wdata), .m_wstrb(m_wstrb), + .m_wlast(m_wlast), .m_wvalid(m_wvalid), .m_wready(m_wready), .m_bresp(m_bresp), .m_bvalid(m_bvalid), .m_bready(m_bready), .emio_i(emio_i) ); @@ -75,6 +89,33 @@ module tb; end end + // ---- the write side of the memory model: one burst at a time, with gaps ------------------------ + localparam [48:0] WB_BASE = 49'h0_2000_0000; + reg [63:0] wmem [0:4095]; + reg [48:0] w_addr = 0; + reg w_open = 0; + always @(posedge clk) begin + m_awready <= 1'b0; + m_wready <= 1'b0; + if (m_bvalid && m_bready) m_bvalid <= 1'b0; + if (m_awvalid && !m_awready && !w_open && ($random(seed) & 1) != 0) begin + m_awready <= 1'b1; + w_addr <= m_awaddr; + w_open <= 1'b1; + if (m_awsize != 3'd3 || m_awburst != 2'b01) begin $display("write-back: bad size or burst"); $finish; end + if ({1'b0, m_awaddr[11:0]} + ({5'd0, m_awlen} + 13'd1) * 13'd8 > 13'h1000) begin + $display("write-back: a burst crosses a 4 KiB page at %h", m_awaddr); $finish; + end + end + if (m_wvalid && !m_wready && w_open && ($random(seed) & 3) != 0) m_wready <= 1'b1; + if (m_wvalid && m_wready) begin + if (m_wstrb != 8'hFF) begin $display("write-back: bad strobes"); $finish; end + wmem[(w_addr - WB_BASE) / 8] <= m_wdata; + w_addr <= w_addr + 49'd8; + if (m_wlast) begin w_open <= 1'b0; m_bvalid <= 1'b1; m_bresp <= 2'b00; end + end + end + // ---- AXI-Lite master tasks -------------------------------------------------------------------- // The master drives just after a clock edge and samples the slave's outputs at the edge, as // they were before it: a handshake completes on the edge where valid and ready were both high. @@ -83,7 +124,7 @@ module tb; task axi_write(input [15:0] word, input [31:0] d); begin @(posedge clk); #1; - awaddr = 32'h8000_0000 | ({16'd0, word} << 2); wdata = d; awvalid = 1; wvalid = 1; + awaddr = 32'hA000_0000 | ({16'd0, word} << 2); wdata = d; awvalid = 1; wvalid = 1; t = 0; while (awvalid || wvalid) begin @(posedge clk); @@ -109,7 +150,7 @@ module tb; task axi_read(input [15:0] word, output [31:0] d); begin @(posedge clk); #1; - araddr = 32'h8000_0000 | ({16'd0, word} << 2); arvalid = 1; + araddr = 32'hA000_0000 | ({16'd0, word} << 2); arvalid = 1; t = 0; while (arvalid) begin @(posedge clk); @@ -133,8 +174,8 @@ module tb; end endtask - integer fd, r, errors, reads, k, n, polls, longest; - reg [31:0] op, a, d, m, got; + integer fd, r, errors, reads, k, n, i, polls, longest; + reg [31:0] op, a, d, m, got, word; reg [63:0] ent_a, ent_d; initial begin @@ -203,6 +244,36 @@ module tb; if (polls > 2000000) begin $display("TIMEOUT sequencer at line %0d", line); $finish; end end if (polls > longest) longest = polls; + end else if (op == "K") begin + // the results of the last batch, written back: n chains, six words each, to check + r = $fscanf(fd, " %d", n); + axi_read(16'h8025, d); + axi_write(16'h8020, WB_BASE[31:0]); + axi_write(16'h8021, {15'd0, WB_BASE[48:32]}); + axi_write(16'h8022, 0); + axi_write(16'h8023, n); + axi_write(16'h8024, 1); + got = 8; polls = 0; + while (got[3]) begin + axi_read(16'h8001, got); + polls = polls + 1; + if (polls > 200000) begin $display("TIMEOUT write-back at line %0d", line); $finish; end + end + if (got[4]) begin $display("write-back error at line %0d", line); errors = errors + 1; end + axi_read(16'h8025, got); + if (got !== d + 1) begin $display("write-back did not run at line %0d", line); errors = errors + 1; end + for (k = 0; k < n; k = k + 1) begin + for (i = 0; i < 6; i = i + 1) begin + r = $fscanf(fd, " %h", d); + line = line + 1; + word = (i % 2) ? wmem[k * 4 + i / 2][63:32] : wmem[k * 4 + i / 2][31:0]; + reads = reads + 1; + if (word !== d) begin + errors = errors + 1; + if (errors <= 25) $display("MISMATCH line %0d: written back chain %0d word %0d got %h want %h", line, k, i, word, d); + end + end + end end else begin $display("bad op at line %0d", line); $finish; -- 2.54.0 (Apple Git-157) From 5cc90ef292ce2dff19ded6101d949a4d1a234715 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Fri, 6 Nov 2026 13:57:26 -0500 Subject: [PATCH 09/11] wai-quantum-landscape-fpga: the board's measurements of rtl version 2, on both links The class campaign on the EMIO build, the checks on it and on both AXI builds, the joint-statistics test as registered, the processor-to-logic latency, and the power checks of the AXI builds against the EMIO classes. One of the two nulls failed; the README says what that leaves unclaimed. Co-Authored-By: Claude Opus 5.5 --- wai/wai-quantum-landscape-fpga/README.md | 102 +++- .../measurements/2026-11-06/README.md | 18 + .../2026-11-06/axi-final/agree-axi.txt | 31 ++ .../2026-11-06/axi-final/conform-axi.txt | 48 ++ .../2026-11-06/axi-final/joint.txt | 23 + .../2026-11-06/axi-final/latency-axi.txt | 22 + .../measurements/2026-11-06/axi-final/log.txt | 7 + .../2026-11-06/axi-final/power/campaign.txt | 3 + .../2026-11-06/axi-final/power/t0-recall.dat | 195 +++++++ .../2026-11-06/axi-final/throughput-axi.txt | 2 + .../2026-11-06/axi-first/agree-axi.txt | 31 ++ .../2026-11-06/axi-first/conform-axi.txt | 48 ++ .../2026-11-06/axi-first/joint.txt | 23 + .../2026-11-06/axi-first/latency-axi.txt | 18 + .../measurements/2026-11-06/axi-first/log.txt | 7 + .../2026-11-06/axi-first/power/campaign.txt | 4 + .../2026-11-06/axi-first/power/null.dat | 505 ++++++++++++++++++ .../2026-11-06/axi-first/power/t0-recall.dat | 195 +++++++ .../2026-11-06/axi-first/throughput-axi.txt | 2 + .../2026-11-06/emio-checks/agree-emio.txt | 31 ++ .../2026-11-06/emio-checks/conform-emio.txt | 48 ++ .../2026-11-06/emio-checks/latency-emio.txt | 7 + .../2026-11-06/emio-checks/ps-ports.txt | 1 + .../emio-checks/throughput-emio.txt | 2 + .../2026-11-06/emio-classes/campaign.txt | 11 + .../2026-11-06/first-attempt/campaign.txt | 6 + 26 files changed, 1385 insertions(+), 5 deletions(-) create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/README.md create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/agree-axi.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/conform-axi.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/joint.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/latency-axi.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/log.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/power/campaign.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/power/t0-recall.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/throughput-axi.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/agree-axi.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/conform-axi.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/joint.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/latency-axi.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/log.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/power/campaign.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/power/null.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/power/t0-recall.dat create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/throughput-axi.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/agree-emio.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/conform-emio.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/latency-emio.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/ps-ports.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/throughput-emio.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/campaign.txt create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/first-attempt/campaign.txt diff --git a/wai/wai-quantum-landscape-fpga/README.md b/wai/wai-quantum-landscape-fpga/README.md index 74bbcc137..580c1dd86 100644 --- a/wai/wai-quantum-landscape-fpga/README.md +++ b/wai/wai-quantum-landscape-fpga/README.md @@ -143,9 +143,11 @@ bitstream load needs it set to 0 first; `scripts/run_all.sh` does that and puts §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 `9617a1da32c1fbd6eba19d4abb2f5a11f9122a3c3c41abde48e316e4f703f62a` (the AXI build) and -`feea5015b2f8d85049fd48bcc66493a411d551326365f67b1db2ae65ec00074c` (the EMIO build); whether a -rebuild gives the same bytes has not been checked. +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 @@ -202,9 +204,99 @@ simulator was not tried. ## What it measured Every figure: **board-level, one board, one meter, this design.** The sensor reads the whole board -(about 3.1 W idle), so these are differences of a few percent of the total. +(about 3.1 W idle), so the power figures are differences of a few percent of the total. + +### 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`.** 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`. +- **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), 100,000 times +(5,000 for EMIO); medians, with the 99th percentile where it differs. + +| access | median | +|---|---| +| EMIO register read, handshake over GPIO (EMIO build) | 930 ns | +| EMIO register write, handshake over GPIO (EMIO build) | 790 ns | +| a GPIO register read (the AXI link's signature) | 230 ns | +| AXI-Lite read, a link register / an engine register | 230 ns / 240 ns | +| AXI-Lite write, to its response (store, then DSB) | 250 ns (p99 380) | +| AXI-Lite writes back to back, posted | 51 ns each | +| a guarded read, as every job access goes (signature, then read) | 470 ns | +| a write list by DMA: 1 entry, kick to done / 512 entries (one page) | 7.6 µs / 20.3 µs, 39.7 ns an entry | +| a batch's results by DMA: 128 chains (one page), kick to read | 37.6 µs, 293 ns a chain | + +End to end: + +| | 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 | 9.4 µs a chain | 4.34 µs a chain (230,000 a second) | + +The engine itself spends about 1.6 µs on such a chain; the rest is the host's. + +### 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 nJ a cell draw | 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) | its own | 73.8 ± 10.3 mW (8 passes) | 61.7 ± 6.5, within 2 combined σ | passes | +| 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`). During the class +campaign a status read over SSH touched the board once, at about 13:25 UTC, during the settle class. -MEASURED_V2_PLACEHOLDER **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 diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/README.md b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/README.md new file mode 100644 index 000000000..5001d33b3 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/README.md @@ -0,0 +1,18 @@ +# 2026-11-06: rtl version 2, on the EMIO link and on the AXI link + +Raw output as the board wrote it. Board-level, one board, one meter. The `*.txt` files at the top +are the predictions and pre-registrations, each committed before the measurement it covers. + +| file | what | +|---|---| +| `PREDICTION.txt` | the class predictions for rtl version 2 | +| `first-attempt/` | 08:01 UTC on the EMIO build: the run/halt step and the known-answer vehicle; then the reload refused (the session check matched its own parent process, since fixed) | +| `emio-classes/` | 12:39 to 14:55 UTC on the EMIO build (sha256 `feea5015...`): step, null, settle, sampling, idle, the settle job at 50 MHz | +| `emio-checks/` | the same build afterwards: conformance, agreement, latency, throughput, the processor's port widths | +| `NULL-REPEAT.txt`, `AXI-PREDICTION.txt` | written after the EMIO null failed, before the AXI build was metered | +| `axi-first/` | the first AXI build (sha256 `3d84dbd3...`, results read back by register reads): checks, then `power/` (the settle check and the repeated null) | +| `JOINT-PREREG.txt` | the joint-statistics test, registered before its first board run (`axi-first/joint.txt`) | +| `AXI-FINAL-PREDICTION.txt`, `axi-final/` | the final AXI build (sha256 `bb825055...`, results written back by DMA): checks, then `power/` (the settle check) | + +Summarise a meter log with `wai-quantum-landscape-fpga analyze ` or +`python3 scripts/power_tables.py `. diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/agree-axi.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/agree-axi.txt new file mode 100644 index 000000000..0153ebac5 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/agree-axi.txt @@ -0,0 +1,31 @@ +certified by the host on the conformance set: settle bit-exact, sample bit-exact +parity earned on this board: settle bit-exact, sample bit-exact +the worked examples on the board and on the CPU reference, 200 seeds each + [PASS] talks at its own temperature: 200/200 answers identical + [PASS] talks at temperature 0: 200/200 answers identical + [PASS] clusters at its own temperature: 200/200 answers identical + [PASS] clusters at temperature 0: 200/200 answers identical + [PASS] selection at its own temperature: 200/200 answers identical + [PASS] selection at temperature 0: 200/200 answers identical + [PASS] chain at its own temperature: 200/200 answers identical + [PASS] chain at temperature 0: 200/200 answers identical + [PASS] assign-2x2 at its own temperature: 200/200 answers identical + [PASS] assign-2x2 at temperature 0: 200/200 answers identical + [PASS] blocks at its own temperature: 200/200 answers identical + [PASS] blocks at temperature 0: 200/200 answers identical + [PASS] assign-3x3 at its own temperature: 200/200 answers identical + [PASS] assign-3x3 at temperature 0: 200/200 answers identical + [PASS] colour-six at its own temperature: 200/200 answers identical + [PASS] colour-six at temperature 0: 200/200 answers identical + [PASS] colour-four at its own temperature: 200/200 answers identical + [PASS] colour-four at temperature 0: 200/200 answers identical + [PASS] piles at its own temperature: 200/200 answers identical + [PASS] piles at temperature 0: 200/200 answers identical + [PASS] cold-constraint at its own temperature: 200/200 answers identical + [PASS] cold-constraint at temperature 0: 200/200 answers identical + [PASS] cold-start at its own temperature: 200/200 answers identical + [PASS] cold-start at temperature 0: 200/200 answers identical + [PASS] too-costly at its own temperature: 200/200 answers identical + [PASS] too-costly at temperature 0: 200/200 answers identical + [PASS] typo: refused by the reader before any member runs: Line 6: `oppos` is not a statement epuML knows. Did you mean `oppose`? +27 checks, 0 failed diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/conform-axi.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/conform-axi.txt new file mode 100644 index 000000000..e7903e3ca --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/conform-axi.txt @@ -0,0 +1,48 @@ +wai.quantum.landscape/1 member tests (quantum-conformance/landscape/vectors.json) +settle cases 13/13 bit for bit; chains 42/42 bit for bit + [PASS] settle kernel: the settle cases, bit for bit (state, sweeps, ending, accumulates), each held to the job + [PASS] sample kernel: the chain vectors, bit for bit (state, sweeps, ending, accumulates, draws), each held to the job + [PASS] cells: 650/650 cut points the host computes for the engine equal the set's, bit for bit + [PASS] cell h=-40 c=0 T=0.25: 100000 draws, counts [100000, 0, 0], z +0.00 +0.00 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-40 c=2 T=1: 100000 draws, counts [100000, 0, 0], z +0.00 +0.00 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-40 c=8 T=2.9: 100000 draws, counts [100000, 0, 0], z +1.27 -1.27 -0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-17 c=1 T=25: 100000 draws, counts [55805, 29689, 14506], z -1.59 +0.91 +1.07 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-17 c=8 T=0.7: 100000 draws, counts [99999, 1, 0], z -1.45 +1.45 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-5 c=1 T=1.8708286933869707: 100000 draws, counts [89058, 10527, 415], z -0.17 +0.28 -0.48 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-5 c=3 T=13.7: 100000 draws, counts [42641, 36802, 20557], z +0.11 -0.21 +0.12 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-3 c=1 T=0.3: 100000 draws, counts [99874, 126, 0], z +0.10 -0.10 -0.01 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-3 c=3 T=1.3: 100000 draws, counts [50015, 49537, 448], z +1.65 -1.37 -2.01 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-1 c=0 T=7.1: 100000 draws, counts [38122, 33245, 28633], z -0.00 +0.88 -0.91 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-1 c=3 T=0.25: 100000 draws, counts [32, 99968, 0], z -0.27 +0.27 -0.11 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=0 c=0 T=1: 100000 draws, counts [33342, 33254, 33404], z +0.06 -0.53 +0.47 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=0 c=2 T=2.9: 100000 draws, counts [24786, 50120, 25094], z -1.88 +1.31 +0.37 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=0 c=8 T=25: 100000 draws, counts [29353, 41113, 29534], z -1.79 +2.16 -0.53 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=1 c=2 T=0.7: 100000 draws, counts [1130, 79767, 19103], z +0.97 -0.12 -0.14 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=1 c=8 T=1.8708286933869707: 100000 draws, counts [801, 96844, 2355], z +0.43 -1.26 +1.20 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=2 c=1 T=13.7: 100000 draws, counts [27835, 34700, 37465], z -0.48 -0.22 +0.66 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=2 c=8 T=0.3: 100000 draws, counts [0, 100000, 0], z -0.00 +0.01 -0.01 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=3 c=1 T=1.3: 100000 draws, counts [835, 17462, 81703], z +0.94 -0.59 +0.36 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=3 c=3 T=7.1: 100000 draws, counts [17600, 41216, 41184], z -0.66 +0.36 +0.15 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=5 c=1 T=0.25: 100000 draws, counts [0, 0, 100000], z +0.00 -0.11 +0.11 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=5 c=3 T=1: 100000 draws, counts [9, 12006, 87985], z +2.50 +0.84 -0.89 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=9 c=0 T=2.9: 100000 draws, counts [204, 4140, 95656], z +0.83 -2.31 +2.09 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=9 c=2 T=25: 100000 draws, counts [21552, 33829, 44619], z -1.18 +0.85 +0.17 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=17 c=0 T=0.7: 100000 draws, counts [0, 0, 100000], z +0.00 -0.00 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=17 c=2 T=1.8708286933869707: 100000 draws, counts [0, 36, 99964], z -0.04 +0.53 -0.53 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=17 c=8 T=13.7: 100000 draws, counts [5263, 32393, 62344], z +0.64 +0.21 -0.50 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=40 c=2 T=0.3: 100000 draws, counts [0, 0, 100000], z +0.00 +0.00 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=40 c=8 T=1.3: 100000 draws, counts [0, 0, 100000], z +0.00 -0.00 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] battery one-pair: chi-square 10.7 on 8 degrees of freedom (critical 28.0) + [PASS] battery a-pair-apart: chi-square 45.3 on 26 degrees of freedom (critical 62.1) + [PASS] battery a-pair-far-apart: chi-square 75.3 on 79 degrees of freedom (critical 141.8) + [PASS] battery a-triangle: chi-square 31.0 on 26 degrees of freedom (critical 62.1) + [PASS] battery wide-fields-and-cost: chi-square 18.3 on 22 degrees of freedom (critical 55.2) + [PASS] battery a-small-field-cold: chi-square 6.9 on 4 degrees of freedom (critical 18.1) + [PASS] battery ending-at-zero: chi-square 17.1 on 11 degrees of freedom (critical 34.5) + [PASS] battery cooling: chi-square 16.8 on 23 degrees of freedom (critical 56.9) + [PASS] the all-at-once control (every cell moved together) fails the battery on ["one-pair", "a-pair-apart", "a-pair-far-apart", "a-triangle", "a-small-field-cold", "ending-at-zero", "cooling"] + [PASS] jobs: 31/31 requests answer as the set says (state, energy, verdict, mode, restarts, sweeps, accumulates, draws, replay digest, or the refusal) + [PASS] 2000/2000 random workloads (2-64 cells, fields -5..5, cost 0-3) settle exactly as the reference does + [PASS] 4096/4096 fields read back equal W s + b (dense W, fields across the engine's ±524287) + [PASS] the engine still answers with its ID at the end: no other session replaced it +44 checks, 0 failed diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/joint.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/joint.txt new file mode 100644 index 000000000..f217931d4 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/joint.txt @@ -0,0 +1,23 @@ +joint statistics against exact enumeration (chains scaled by 1, seed 2027) + ring 8, fixed T = 1.5, 4 sweeps: 200000 chains, 2930 bins; exact law: chi2 3030.0 (p 0.095), G 3066.8 (p 0.037), TV 0.0436, cell |z| 1.91 (p 1.000), pair |z| 2.61 (p 1.000); same as the reference 200000/200000; 3.3 s + control, the same marginals with independent cells: chi2 244079.4 on 3247 bins (p 0.00e0), cell |z| 1.91 (p 1.000), pair |z| 86.9 (p 0.00e0) + [PASS] ring 8, fixed T = 1.5, 4 sweeps: the joint law is the exact law (every p at least 0.001) + [PASS] ring 8, fixed T = 1.5, 4 sweeps: every chain's final state is the reference's + [PASS] ring 8, fixed T = 1.5, 4 sweeps: the independent-cells control is rejected (chi-square p below 1e-6) + ring 8, the job's anneal from T = 2.5, 10 sweeps: 200000 chains, 45 bins; exact law: chi2 38.5 (p 0.708), G 38.6 (p 0.702), TV 0.0022, cell |z| 1.20 (p 1.000), pair |z| 2.35 (p 1.000); same as the reference 200000/200000; 5.7 s + control, the same marginals with independent cells: chi2 1933336.3 on 181 bins (p 0.00e0), cell |z| 1.20 (p 1.000), pair |z| 390.3 (p 0.00e0) + [PASS] ring 8, the job's anneal from T = 2.5, 10 sweeps: the joint law is the exact law (every p at least 0.001) + [PASS] ring 8, the job's anneal from T = 2.5, 10 sweeps: every chain's final state is the reference's + [PASS] ring 8, the job's anneal from T = 2.5, 10 sweeps: the independent-cells control is rejected (chi-square p below 1e-6) + dense 6, fixed T = 4, 2 sweeps: 200000 chains, 729 bins; exact law: chi2 682.6 (p 0.884), G 685.3 (p 0.870), TV 0.0219, cell |z| 1.73 (p 1.000), pair |z| 3.58 (p 0.047); same as the reference 200000/200000; 1.7 s + control, the same marginals with independent cells: chi2 91995.4 on 729 bins (p 0.00e0), cell |z| 1.73 (p 1.000), pair |z| 54.5 (p 0.00e0) + [PASS] dense 6, fixed T = 4, 2 sweeps: the joint law is the exact law (every p at least 0.001) + [PASS] dense 6, fixed T = 4, 2 sweeps: every chain's final state is the reference's + [PASS] dense 6, fixed T = 4, 2 sweeps: the independent-cells control is rejected (chi-square p below 1e-6) + sparse 10, the job's anneal from T = 1.5, 8 sweeps: 400000 chains, 190 bins; exact law: chi2 196.1 (p 0.346), G 207.7 (p 0.167), TV 0.0048, cell |z| 1.45 (p 1.000), pair |z| 3.04 (p 0.773); same as the reference 400000/400000; 11.4 s + control, the same marginals with independent cells: chi2 14139391.3 on 1531 bins (p 0.00e0), cell |z| 1.45 (p 1.000), pair |z| 373.6 (p 0.00e0) + [PASS] sparse 10, the job's anneal from T = 1.5, 8 sweeps: the joint law is the exact law (every p at least 0.001) + [PASS] sparse 10, the job's anneal from T = 1.5, 8 sweeps: every chain's final state is the reference's + [PASS] sparse 10, the job's anneal from T = 1.5, 8 sweeps: the independent-cells control is rejected (chi-square p below 1e-6) + [PASS] the engine still answers with its ID at the end: no other session replaced it +13 checks, 0 failed diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/latency-axi.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/latency-axi.txt new file mode 100644 index 000000000..9fa077542 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/latency-axi.txt @@ -0,0 +1,22 @@ +processor-to-logic latency, AXI link: one board, the engine at its measured clock + timer: CNTVCT_EL0 at 99.999 MHz, one tick 10.0 ns + the timer alone (two reads, an ISB each) median 10 ns p10 10 p90 20 p99 20 max 20 mean 11 (n 20000) + EMIO signature read (a processor GPIO register) median 230 ns p10 230 p90 240 p99 240 max 89851 mean 237 (n 100000) + AXI-Lite read, a link register median 230 ns p10 230 p90 230 p99 230 max 19920 mean 233 (n 100000) + AXI-Lite read, an engine register median 240 ns p10 240 p90 240 p99 240 max 25680 mean 242 (n 100000) + a DSB alone median 10 ns p10 10 p90 110 p99 150 max 10470 mean 28 (n 100000) + AXI-Lite write and its response (DSB), a link register median 250 ns p10 250 p90 350 p99 380 max 22460 mean 269 (n 100000) + AXI-Lite write and its response (DSB), an engine register median 250 ns p10 250 p90 350 p99 380 max 56791 mean 269 (n 100000) + AXI-Lite writes back to back, no DSB between: 51 ns each over 100000 + AXI-Lite write then read (select a cell, read its field) median 460 ns p10 460 p90 580 p99 600 max 53021 mean 481 (n 100000) + guarded read, as every job access goes (signature, then read) median 470 ns p10 460 p90 470 p99 470 max 21530 mean 470 (n 100000) + a 1-entry write list by DMA, kick to done (4 writes, polls) median 7640 ns p10 7570 p90 7790 p99 9340 max 25820 mean 7775 (n 5000) + a 512-entry write list by DMA (one page), kick to done median 20310 ns p10 20010 p90 20480 p99 23510 max 54711 mean 20433 (n 2000) + = 39.7 ns an entry, 25.2 M register writes/s + 128 chains' results written back (one page), kick to read median 37550 ns p10 37240 p90 37990 p99 47880 max 49670 mean 37825 (n 2000) + = 293 ns a chain + AXI, DMA and sequencer: a full table (64 levels x 128 fields, 32835 register writes): median 3.81 ms of 6, 116 ns a write + AXI, DMA and sequencer: 65536 chains of 8 cells x 4 sweeps: 0.285 s, 4.34 us a chain, 230153 chains/s + AXI, plain register accesses: a full table (64 levels x 128 fields, 32835 register writes): median 17.46 ms of 6, 532 ns a write + AXI, plain register accesses: 16384 chains of 8 cells x 4 sweeps: 0.154 s, 9.42 us a chain, 106113 chains/s + AXI accesses 1174977, DMA lists 8862, sequencer batches 257, write-backs 2714 diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/log.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/log.txt new file mode 100644 index 000000000..5c200e7fd --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/log.txt @@ -0,0 +1,7 @@ +2026-11-06T18:43:55Z start: engine answers: ID and version 0x00404002; measured clock 99.997 MHz +2026-11-06T18:44:09Z conform exit=0 +2026-11-06T18:44:20Z agree exit=0 +2026-11-06T18:44:42Z joint exit=0 +2026-11-06T18:44:44Z latency exit=0 +2026-11-06T18:44:48Z throughput exit=0 +2026-11-06T18:44:48Z end: engine answers: ID and version 0x00404002; measured clock 99.998 MHz diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/power/campaign.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/power/campaign.txt new file mode 100644 index 000000000..51583a7d5 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/power/campaign.txt @@ -0,0 +1,3 @@ +2026-11-06T18:45:00Z start: engine answers: ID and version 0x00404002; measured clock 99.998 MHz +2026-11-06T18:54:01Z t0-recall done exit=0 +2026-11-06T18:54:01Z end diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/power/t0-recall.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/power/t0-recall.dat new file mode 100644 index 000000000..db3c2ee6a --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/power/t0-recall.dat @@ -0,0 +1,195 @@ +# class t0-recall cores 1 link AXI +# wai-quantum-landscape-fpga meter passes=3 settle=60s samples=30 null=false sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793990700, tv_nsec: 679432036 } +# pass state sample power_uW +1 halt 1 3150000 +1 halt 2 3140000 +1 halt 3 3140000 +1 halt 4 3150000 +1 halt 5 3140000 +1 halt 6 3140000 +1 halt 7 3150000 +1 halt 8 3140000 +1 halt 9 3140000 +1 halt 10 3150000 +1 halt 11 3150000 +1 halt 12 3140000 +1 halt 13 3140000 +1 halt 14 3140000 +1 halt 15 3150000 +1 halt 16 3150000 +1 halt 17 3150000 +1 halt 18 3140000 +1 halt 19 3140000 +1 halt 20 3140000 +1 halt 21 3150000 +1 halt 22 3150000 +1 halt 23 3140000 +1 halt 24 3140000 +1 halt 25 3140000 +1 halt 26 3140000 +1 halt 27 3150000 +1 halt 28 3180000 +1 halt 29 3140000 +1 halt 30 3140000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997965 +1 run 1 3310000 +1 run 2 3320000 +1 run 3 3310000 +1 run 4 3300000 +1 run 5 3300000 +1 run 6 3300000 +1 run 7 3320000 +1 run 8 3300000 +1 run 9 3300000 +1 run 10 3330000 +1 run 11 3300000 +1 run 12 3310000 +1 run 13 3300000 +1 run 14 3300000 +1 run 15 3300000 +1 run 16 3300000 +1 run 17 3300000 +1 run 18 3300000 +1 run 19 3300000 +1 run 20 3310000 +1 run 21 3310000 +1 run 22 3300000 +1 run 23 3300000 +1 run 24 3300000 +1 run 25 3330000 +1 run 26 3300000 +1 run 27 3300000 +1 run 28 3300000 +1 run 29 3300000 +1 run 30 3300000 +# point pass=1 state=run jobs_per_s=12499746.7 updates_per_s=0.0 clock_hz=99997964 +2 run 1 3330000 +2 run 2 3310000 +2 run 3 3310000 +2 run 4 3320000 +2 run 5 3310000 +2 run 6 3320000 +2 run 7 3310000 +2 run 8 3320000 +2 run 9 3330000 +2 run 10 3330000 +2 run 11 3310000 +2 run 12 3320000 +2 run 13 3350000 +2 run 14 3310000 +2 run 15 3310000 +2 run 16 3300000 +2 run 17 3310000 +2 run 18 3330000 +2 run 19 3300000 +2 run 20 3320000 +2 run 21 3320000 +2 run 22 3310000 +2 run 23 3310000 +2 run 24 3300000 +2 run 25 3320000 +2 run 26 3320000 +2 run 27 3320000 +2 run 28 3590000 +2 run 29 3610000 +2 run 30 3310000 +# point pass=2 state=run jobs_per_s=12499746.9 updates_per_s=0.0 clock_hz=99997966 +2 halt 1 3420000 +2 halt 2 3420000 +2 halt 3 3130000 +2 halt 4 3140000 +2 halt 5 3130000 +2 halt 6 3160000 +2 halt 7 3150000 +2 halt 8 3140000 +2 halt 9 3130000 +2 halt 10 3140000 +2 halt 11 3140000 +2 halt 12 3140000 +2 halt 13 3130000 +2 halt 14 3130000 +2 halt 15 3150000 +2 halt 16 3140000 +2 halt 17 3150000 +2 halt 18 3140000 +2 halt 19 3130000 +2 halt 20 3150000 +2 halt 21 3140000 +2 halt 22 3140000 +2 halt 23 3460000 +2 halt 24 3140000 +2 halt 25 3140000 +2 halt 26 3130000 +2 halt 27 3140000 +2 halt 28 3140000 +2 halt 29 3130000 +2 halt 30 3140000 +# point pass=2 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997939 +3 halt 1 3150000 +3 halt 2 3140000 +3 halt 3 3140000 +3 halt 4 3160000 +3 halt 5 3160000 +3 halt 6 3140000 +3 halt 7 3160000 +3 halt 8 3130000 +3 halt 9 3150000 +3 halt 10 3140000 +3 halt 11 3140000 +3 halt 12 3140000 +3 halt 13 3150000 +3 halt 14 3140000 +3 halt 15 3140000 +3 halt 16 3140000 +3 halt 17 3140000 +3 halt 18 3140000 +3 halt 19 3140000 +3 halt 20 3140000 +3 halt 21 3140000 +3 halt 22 3140000 +3 halt 23 3150000 +3 halt 24 3140000 +3 halt 25 3140000 +3 halt 26 3140000 +3 halt 27 3150000 +3 halt 28 3140000 +3 halt 29 3140000 +3 halt 30 3140000 +# point pass=3 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997771 +3 run 1 3320000 +3 run 2 3300000 +3 run 3 3290000 +3 run 4 3290000 +3 run 5 3300000 +3 run 6 3290000 +3 run 7 3290000 +3 run 8 3310000 +3 run 9 3310000 +3 run 10 3620000 +3 run 11 3560000 +3 run 12 3300000 +3 run 13 3310000 +3 run 14 3300000 +3 run 15 3300000 +3 run 16 3310000 +3 run 17 3320000 +3 run 18 3300000 +3 run 19 3300000 +3 run 20 3310000 +3 run 21 3300000 +3 run 22 3310000 +3 run 23 3300000 +3 run 24 3300000 +3 run 25 3300000 +3 run 26 3330000 +3 run 27 3320000 +3 run 28 3290000 +3 run 29 3300000 +3 run 30 3310000 +# point pass=3 state=run jobs_per_s=12499726.1 updates_per_s=0.0 clock_hz=99997799 +# done SystemTime { tv_sec: 1793991241, tv_nsec: 34183769 } +# passes used 3 of 3 (dropped: []) +# run - halt = 166.7 +/- 6.7 mW (per-pass sd 11.5); 3/3 passes positive; 8 sample excursions beyond 6 MAD +# per-pass differences (mW): 160.0 180.0 160.0 +# board power while running 3307 mW; jobs/s 12499739.9; updates/s 0.0; measured PL clock 99.998 MHz +# pre-registered prediction 162.0 mW: measured/predicted = 1.03, 0.7 sigma from it diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/throughput-axi.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/throughput-axi.txt new file mode 100644 index 000000000..cf9a5dc7d --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-final/throughput-axi.txt @@ -0,0 +1,2 @@ +settle, dense 64 (recall cue): clock 99.998 MHz, 8.0 clocks a job, 12499745 jobs/s, 0.000e0 cell updates/s, 1.831e11 accumulates/s +sample, sequential, 64 cells, 8 sweeps at T = 0.25: clock 99.998 MHz, 2059.0 clocks a job, 48566 jobs/s, 2.487e7 cell updates/s, 1.138e9 accumulates/s diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/agree-axi.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/agree-axi.txt new file mode 100644 index 000000000..0153ebac5 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/agree-axi.txt @@ -0,0 +1,31 @@ +certified by the host on the conformance set: settle bit-exact, sample bit-exact +parity earned on this board: settle bit-exact, sample bit-exact +the worked examples on the board and on the CPU reference, 200 seeds each + [PASS] talks at its own temperature: 200/200 answers identical + [PASS] talks at temperature 0: 200/200 answers identical + [PASS] clusters at its own temperature: 200/200 answers identical + [PASS] clusters at temperature 0: 200/200 answers identical + [PASS] selection at its own temperature: 200/200 answers identical + [PASS] selection at temperature 0: 200/200 answers identical + [PASS] chain at its own temperature: 200/200 answers identical + [PASS] chain at temperature 0: 200/200 answers identical + [PASS] assign-2x2 at its own temperature: 200/200 answers identical + [PASS] assign-2x2 at temperature 0: 200/200 answers identical + [PASS] blocks at its own temperature: 200/200 answers identical + [PASS] blocks at temperature 0: 200/200 answers identical + [PASS] assign-3x3 at its own temperature: 200/200 answers identical + [PASS] assign-3x3 at temperature 0: 200/200 answers identical + [PASS] colour-six at its own temperature: 200/200 answers identical + [PASS] colour-six at temperature 0: 200/200 answers identical + [PASS] colour-four at its own temperature: 200/200 answers identical + [PASS] colour-four at temperature 0: 200/200 answers identical + [PASS] piles at its own temperature: 200/200 answers identical + [PASS] piles at temperature 0: 200/200 answers identical + [PASS] cold-constraint at its own temperature: 200/200 answers identical + [PASS] cold-constraint at temperature 0: 200/200 answers identical + [PASS] cold-start at its own temperature: 200/200 answers identical + [PASS] cold-start at temperature 0: 200/200 answers identical + [PASS] too-costly at its own temperature: 200/200 answers identical + [PASS] too-costly at temperature 0: 200/200 answers identical + [PASS] typo: refused by the reader before any member runs: Line 6: `oppos` is not a statement epuML knows. Did you mean `oppose`? +27 checks, 0 failed diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/conform-axi.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/conform-axi.txt new file mode 100644 index 000000000..e7903e3ca --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/conform-axi.txt @@ -0,0 +1,48 @@ +wai.quantum.landscape/1 member tests (quantum-conformance/landscape/vectors.json) +settle cases 13/13 bit for bit; chains 42/42 bit for bit + [PASS] settle kernel: the settle cases, bit for bit (state, sweeps, ending, accumulates), each held to the job + [PASS] sample kernel: the chain vectors, bit for bit (state, sweeps, ending, accumulates, draws), each held to the job + [PASS] cells: 650/650 cut points the host computes for the engine equal the set's, bit for bit + [PASS] cell h=-40 c=0 T=0.25: 100000 draws, counts [100000, 0, 0], z +0.00 +0.00 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-40 c=2 T=1: 100000 draws, counts [100000, 0, 0], z +0.00 +0.00 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-40 c=8 T=2.9: 100000 draws, counts [100000, 0, 0], z +1.27 -1.27 -0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-17 c=1 T=25: 100000 draws, counts [55805, 29689, 14506], z -1.59 +0.91 +1.07 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-17 c=8 T=0.7: 100000 draws, counts [99999, 1, 0], z -1.45 +1.45 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-5 c=1 T=1.8708286933869707: 100000 draws, counts [89058, 10527, 415], z -0.17 +0.28 -0.48 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-5 c=3 T=13.7: 100000 draws, counts [42641, 36802, 20557], z +0.11 -0.21 +0.12 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-3 c=1 T=0.3: 100000 draws, counts [99874, 126, 0], z +0.10 -0.10 -0.01 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-3 c=3 T=1.3: 100000 draws, counts [50015, 49537, 448], z +1.65 -1.37 -2.01 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-1 c=0 T=7.1: 100000 draws, counts [38122, 33245, 28633], z -0.00 +0.88 -0.91 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-1 c=3 T=0.25: 100000 draws, counts [32, 99968, 0], z -0.27 +0.27 -0.11 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=0 c=0 T=1: 100000 draws, counts [33342, 33254, 33404], z +0.06 -0.53 +0.47 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=0 c=2 T=2.9: 100000 draws, counts [24786, 50120, 25094], z -1.88 +1.31 +0.37 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=0 c=8 T=25: 100000 draws, counts [29353, 41113, 29534], z -1.79 +2.16 -0.53 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=1 c=2 T=0.7: 100000 draws, counts [1130, 79767, 19103], z +0.97 -0.12 -0.14 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=1 c=8 T=1.8708286933869707: 100000 draws, counts [801, 96844, 2355], z +0.43 -1.26 +1.20 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=2 c=1 T=13.7: 100000 draws, counts [27835, 34700, 37465], z -0.48 -0.22 +0.66 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=2 c=8 T=0.3: 100000 draws, counts [0, 100000, 0], z -0.00 +0.01 -0.01 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=3 c=1 T=1.3: 100000 draws, counts [835, 17462, 81703], z +0.94 -0.59 +0.36 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=3 c=3 T=7.1: 100000 draws, counts [17600, 41216, 41184], z -0.66 +0.36 +0.15 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=5 c=1 T=0.25: 100000 draws, counts [0, 0, 100000], z +0.00 -0.11 +0.11 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=5 c=3 T=1: 100000 draws, counts [9, 12006, 87985], z +2.50 +0.84 -0.89 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=9 c=0 T=2.9: 100000 draws, counts [204, 4140, 95656], z +0.83 -2.31 +2.09 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=9 c=2 T=25: 100000 draws, counts [21552, 33829, 44619], z -1.18 +0.85 +0.17 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=17 c=0 T=0.7: 100000 draws, counts [0, 0, 100000], z +0.00 -0.00 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=17 c=2 T=1.8708286933869707: 100000 draws, counts [0, 36, 99964], z -0.04 +0.53 -0.53 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=17 c=8 T=13.7: 100000 draws, counts [5263, 32393, 62344], z +0.64 +0.21 -0.50 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=40 c=2 T=0.3: 100000 draws, counts [0, 0, 100000], z +0.00 +0.00 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=40 c=8 T=1.3: 100000 draws, counts [0, 0, 100000], z +0.00 -0.00 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] battery one-pair: chi-square 10.7 on 8 degrees of freedom (critical 28.0) + [PASS] battery a-pair-apart: chi-square 45.3 on 26 degrees of freedom (critical 62.1) + [PASS] battery a-pair-far-apart: chi-square 75.3 on 79 degrees of freedom (critical 141.8) + [PASS] battery a-triangle: chi-square 31.0 on 26 degrees of freedom (critical 62.1) + [PASS] battery wide-fields-and-cost: chi-square 18.3 on 22 degrees of freedom (critical 55.2) + [PASS] battery a-small-field-cold: chi-square 6.9 on 4 degrees of freedom (critical 18.1) + [PASS] battery ending-at-zero: chi-square 17.1 on 11 degrees of freedom (critical 34.5) + [PASS] battery cooling: chi-square 16.8 on 23 degrees of freedom (critical 56.9) + [PASS] the all-at-once control (every cell moved together) fails the battery on ["one-pair", "a-pair-apart", "a-pair-far-apart", "a-triangle", "a-small-field-cold", "ending-at-zero", "cooling"] + [PASS] jobs: 31/31 requests answer as the set says (state, energy, verdict, mode, restarts, sweeps, accumulates, draws, replay digest, or the refusal) + [PASS] 2000/2000 random workloads (2-64 cells, fields -5..5, cost 0-3) settle exactly as the reference does + [PASS] 4096/4096 fields read back equal W s + b (dense W, fields across the engine's ±524287) + [PASS] the engine still answers with its ID at the end: no other session replaced it +44 checks, 0 failed diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/joint.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/joint.txt new file mode 100644 index 000000000..9751b5fef --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/joint.txt @@ -0,0 +1,23 @@ +joint statistics against exact enumeration (chains scaled by 1, seed 2027) + ring 8, fixed T = 1.5, 4 sweeps: 200000 chains, 2930 bins; exact law: chi2 3030.0 (p 0.095), G 3066.8 (p 0.037), TV 0.0436, cell |z| 1.91 (p 1.000), pair |z| 2.61 (p 1.000); same as the reference 200000/200000; 3.7 s + control, the same marginals with independent cells: chi2 244079.4 on 3247 bins (p 0.00e0), cell |z| 1.91 (p 1.000), pair |z| 86.9 (p 0.00e0) + [PASS] ring 8, fixed T = 1.5, 4 sweeps: the joint law is the exact law (every p at least 0.001) + [PASS] ring 8, fixed T = 1.5, 4 sweeps: every chain's final state is the reference's + [PASS] ring 8, fixed T = 1.5, 4 sweeps: the independent-cells control is rejected (chi-square p below 1e-6) + ring 8, the job's anneal from T = 2.5, 10 sweeps: 200000 chains, 45 bins; exact law: chi2 38.5 (p 0.708), G 38.6 (p 0.702), TV 0.0022, cell |z| 1.20 (p 1.000), pair |z| 2.35 (p 1.000); same as the reference 200000/200000; 6.2 s + control, the same marginals with independent cells: chi2 1933336.3 on 181 bins (p 0.00e0), cell |z| 1.20 (p 1.000), pair |z| 390.3 (p 0.00e0) + [PASS] ring 8, the job's anneal from T = 2.5, 10 sweeps: the joint law is the exact law (every p at least 0.001) + [PASS] ring 8, the job's anneal from T = 2.5, 10 sweeps: every chain's final state is the reference's + [PASS] ring 8, the job's anneal from T = 2.5, 10 sweeps: the independent-cells control is rejected (chi-square p below 1e-6) + dense 6, fixed T = 4, 2 sweeps: 200000 chains, 729 bins; exact law: chi2 682.6 (p 0.884), G 685.3 (p 0.870), TV 0.0219, cell |z| 1.73 (p 1.000), pair |z| 3.58 (p 0.047); same as the reference 200000/200000; 2.1 s + control, the same marginals with independent cells: chi2 91995.4 on 729 bins (p 0.00e0), cell |z| 1.73 (p 1.000), pair |z| 54.5 (p 0.00e0) + [PASS] dense 6, fixed T = 4, 2 sweeps: the joint law is the exact law (every p at least 0.001) + [PASS] dense 6, fixed T = 4, 2 sweeps: every chain's final state is the reference's + [PASS] dense 6, fixed T = 4, 2 sweeps: the independent-cells control is rejected (chi-square p below 1e-6) + sparse 10, the job's anneal from T = 1.5, 8 sweeps: 400000 chains, 190 bins; exact law: chi2 196.1 (p 0.346), G 207.7 (p 0.167), TV 0.0048, cell |z| 1.45 (p 1.000), pair |z| 3.04 (p 0.773); same as the reference 400000/400000; 12.4 s + control, the same marginals with independent cells: chi2 14139391.3 on 1531 bins (p 0.00e0), cell |z| 1.45 (p 1.000), pair |z| 373.6 (p 0.00e0) + [PASS] sparse 10, the job's anneal from T = 1.5, 8 sweeps: the joint law is the exact law (every p at least 0.001) + [PASS] sparse 10, the job's anneal from T = 1.5, 8 sweeps: every chain's final state is the reference's + [PASS] sparse 10, the job's anneal from T = 1.5, 8 sweeps: the independent-cells control is rejected (chi-square p below 1e-6) + [PASS] the engine still answers with its ID at the end: no other session replaced it +13 checks, 0 failed diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/latency-axi.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/latency-axi.txt new file mode 100644 index 000000000..fc5f76b6c --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/latency-axi.txt @@ -0,0 +1,18 @@ +processor-to-logic latency, AXI link: one board, the engine at its measured clock + timer: CNTVCT_EL0 at 99.999 MHz, one tick 10.0 ns + the timer alone (two reads, an ISB each) median 10 ns p10 10 p90 20 p99 20 max 18810 mean 12 (n 20000) + EMIO signature read (a processor GPIO register) median 230 ns p10 230 p90 240 p99 240 max 28140 mean 236 (n 100000) + AXI-Lite read, a link register median 230 ns p10 230 p90 230 p99 230 max 22360 mean 233 (n 100000) + AXI-Lite read, an engine register median 240 ns p10 240 p90 240 p99 240 max 21600 mean 242 (n 100000) + AXI-Lite write, a link register median 10 ns p10 10 p90 20 p99 20 max 10370 mean 14 (n 100000) + AXI-Lite write, an engine register median 10 ns p10 10 p90 20 p99 20 max 38470 mean 14 (n 100000) + AXI-Lite write then read (select a cell, read its field) median 460 ns p10 460 p90 580 p99 600 max 47820 mean 481 (n 100000) + guarded read, as every job access goes (signature, then read) median 470 ns p10 460 p90 470 p99 470 max 19920 mean 469 (n 100000) + a 1-entry write list by DMA, kick to done (4 writes, polls) median 7460 ns p10 7340 p90 7790 p99 9400 max 28330 mean 7645 (n 5000) + a 512-entry write list by DMA (one page), kick to done median 20020 ns p10 19740 p90 20260 p99 29510 max 53911 mean 20197 (n 2000) + = 39.1 ns an entry, 25.6 M register writes/s + AXI, DMA and sequencer: a full table (64 levels x 128 fields, 32835 register writes): median 3.82 ms of 6, 116 ns a write + AXI, DMA and sequencer: 65536 chains of 8 cells x 4 sweeps: 0.436 s, 6.65 us a chain, 150472 chains/s + AXI, plain register accesses: a full table (64 levels x 128 fields, 32835 register writes): median 17.48 ms of 6, 532 ns a write + AXI, plain register accesses: 16384 chains of 8 cells x 4 sweeps: 0.154 s, 9.43 us a chain, 106053 chains/s + AXI accesses 1387388, DMA lists 8862, sequencer batches 257 diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/log.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/log.txt new file mode 100644 index 000000000..bc6e86a56 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/log.txt @@ -0,0 +1,7 @@ +2026-11-06T15:24:07Z start: engine answers: ID and version 0x00404002; measured clock 99.998 MHz +2026-11-06T15:24:28Z conform exit=0 +2026-11-06T15:24:39Z agree exit=0 +2026-11-06T15:25:03Z joint exit=0 +2026-11-06T15:25:05Z latency exit=0 +2026-11-06T15:25:09Z throughput exit=0 +2026-11-06T15:25:10Z end: engine answers: ID and version 0x00404002; measured clock 99.998 MHz diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/power/campaign.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/power/campaign.txt new file mode 100644 index 000000000..3e0edd6f1 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/power/campaign.txt @@ -0,0 +1,4 @@ +2026-11-06T15:25:28Z start: engine answers: ID and version 0x00404002; measured clock 99.998 MHz +2026-11-06T15:34:28Z t0-recall done exit=0 +2026-11-06T15:58:29Z null done exit=0 +2026-11-06T15:58:29Z end diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/power/null.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/power/null.dat new file mode 100644 index 000000000..bfb582c85 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/power/null.dat @@ -0,0 +1,505 @@ +# class t0-recall cores 1 link AXI +# wai-quantum-landscape-fpga meter passes=8 settle=60s samples=30 null=true sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793979268, tv_nsec: 870353142 } +# pass state sample power_uW +1 run 1 3140000 +1 run 2 3130000 +1 run 3 3130000 +1 run 4 3130000 +1 run 5 3130000 +1 run 6 3140000 +1 run 7 3120000 +1 run 8 3140000 +1 run 9 3130000 +1 run 10 3120000 +1 run 11 3130000 +1 run 12 3130000 +1 run 13 3140000 +1 run 14 3140000 +1 run 15 3150000 +1 run 16 3130000 +1 run 17 3140000 +1 run 18 3140000 +1 run 19 3130000 +1 run 20 3140000 +1 run 21 3130000 +1 run 22 3130000 +1 run 23 3150000 +1 run 24 3130000 +1 run 25 3140000 +1 run 26 3120000 +1 run 27 3130000 +1 run 28 3140000 +1 run 29 3130000 +1 run 30 3110000 +# point pass=1 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997489 +1 halt 1 3130000 +1 halt 2 3130000 +1 halt 3 3150000 +1 halt 4 3130000 +1 halt 5 3130000 +1 halt 6 3120000 +1 halt 7 3170000 +1 halt 8 3140000 +1 halt 9 3140000 +1 halt 10 3140000 +1 halt 11 3160000 +1 halt 12 3160000 +1 halt 13 3140000 +1 halt 14 3140000 +1 halt 15 3140000 +1 halt 16 3140000 +1 halt 17 3150000 +1 halt 18 3140000 +1 halt 19 3150000 +1 halt 20 3150000 +1 halt 21 3140000 +1 halt 22 3140000 +1 halt 23 3140000 +1 halt 24 3140000 +1 halt 25 3140000 +1 halt 26 3140000 +1 halt 27 3270000 +1 halt 28 3140000 +1 halt 29 3130000 +1 halt 30 3130000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997556 +2 halt 1 3140000 +2 halt 2 3140000 +2 halt 3 3140000 +2 halt 4 3130000 +2 halt 5 3150000 +2 halt 6 3140000 +2 halt 7 3140000 +2 halt 8 3160000 +2 halt 9 3150000 +2 halt 10 3140000 +2 halt 11 3140000 +2 halt 12 3140000 +2 halt 13 3150000 +2 halt 14 3140000 +2 halt 15 3140000 +2 halt 16 3140000 +2 halt 17 3150000 +2 halt 18 3140000 +2 halt 19 3140000 +2 halt 20 3150000 +2 halt 21 3150000 +2 halt 22 3150000 +2 halt 23 3140000 +2 halt 24 3150000 +2 halt 25 3140000 +2 halt 26 3140000 +2 halt 27 3140000 +2 halt 28 3140000 +2 halt 29 3140000 +2 halt 30 3140000 +# point pass=2 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997613 +2 run 1 3130000 +2 run 2 3140000 +2 run 3 3140000 +2 run 4 3130000 +2 run 5 3150000 +2 run 6 3140000 +2 run 7 3130000 +2 run 8 3140000 +2 run 9 3130000 +2 run 10 3140000 +2 run 11 3140000 +2 run 12 3140000 +2 run 13 3140000 +2 run 14 3130000 +2 run 15 3140000 +2 run 16 3140000 +2 run 17 3130000 +2 run 18 3140000 +2 run 19 3140000 +2 run 20 3140000 +2 run 21 3130000 +2 run 22 3130000 +2 run 23 3130000 +2 run 24 3140000 +2 run 25 3140000 +2 run 26 3140000 +2 run 27 3120000 +2 run 28 3150000 +2 run 29 3160000 +2 run 30 3140000 +# point pass=2 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997660 +3 halt 1 3140000 +3 halt 2 3150000 +3 halt 3 3130000 +3 halt 4 3140000 +3 halt 5 3140000 +3 halt 6 3130000 +3 halt 7 3140000 +3 halt 8 3150000 +3 halt 9 3140000 +3 halt 10 3140000 +3 halt 11 3140000 +3 halt 12 3140000 +3 halt 13 3140000 +3 halt 14 3140000 +3 halt 15 3130000 +3 halt 16 3140000 +3 halt 17 3150000 +3 halt 18 3140000 +3 halt 19 3140000 +3 halt 20 3140000 +3 halt 21 3140000 +3 halt 22 3130000 +3 halt 23 3140000 +3 halt 24 3140000 +3 halt 25 3150000 +3 halt 26 3150000 +3 halt 27 3140000 +3 halt 28 3140000 +3 halt 29 3130000 +3 halt 30 3130000 +# point pass=3 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997700 +3 run 1 3140000 +3 run 2 3140000 +3 run 3 3140000 +3 run 4 3160000 +3 run 5 3140000 +3 run 6 3130000 +3 run 7 3140000 +3 run 8 3130000 +3 run 9 3140000 +3 run 10 3140000 +3 run 11 3140000 +3 run 12 3150000 +3 run 13 3170000 +3 run 14 3140000 +3 run 15 3130000 +3 run 16 3130000 +3 run 17 3150000 +3 run 18 3130000 +3 run 19 3140000 +3 run 20 3140000 +3 run 21 3150000 +3 run 22 3130000 +3 run 23 3140000 +3 run 24 3130000 +3 run 25 3140000 +3 run 26 3140000 +3 run 27 3140000 +3 run 28 3140000 +3 run 29 3130000 +3 run 30 3140000 +# point pass=3 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997734 +4 halt 1 3150000 +4 halt 2 3140000 +4 halt 3 3140000 +4 halt 4 3140000 +4 halt 5 3140000 +4 halt 6 3140000 +4 halt 7 3140000 +4 halt 8 3140000 +4 halt 9 3160000 +4 halt 10 3140000 +4 halt 11 3140000 +4 halt 12 3140000 +4 halt 13 3140000 +4 halt 14 3140000 +4 halt 15 3140000 +4 halt 16 3130000 +4 halt 17 3130000 +4 halt 18 3140000 +4 halt 19 3140000 +4 halt 20 3130000 +4 halt 21 3160000 +4 halt 22 3140000 +4 halt 23 3140000 +4 halt 24 3150000 +4 halt 25 3140000 +4 halt 26 3140000 +4 halt 27 3140000 +4 halt 28 3140000 +4 halt 29 3140000 +4 halt 30 3130000 +# point pass=4 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997763 +4 run 1 3140000 +4 run 2 3140000 +4 run 3 3140000 +4 run 4 3140000 +4 run 5 3150000 +4 run 6 3150000 +4 run 7 3140000 +4 run 8 3140000 +4 run 9 3140000 +4 run 10 3140000 +4 run 11 3140000 +4 run 12 3140000 +4 run 13 3140000 +4 run 14 3140000 +4 run 15 3140000 +4 run 16 3130000 +4 run 17 3140000 +4 run 18 3150000 +4 run 19 3140000 +4 run 20 3140000 +4 run 21 3170000 +4 run 22 3120000 +4 run 23 3140000 +4 run 24 3140000 +4 run 25 3140000 +4 run 26 3150000 +4 run 27 3140000 +4 run 28 3140000 +4 run 29 3150000 +4 run 30 3140000 +# point pass=4 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997789 +5 halt 1 3150000 +5 halt 2 3150000 +5 halt 3 3140000 +5 halt 4 3140000 +5 halt 5 3150000 +5 halt 6 3150000 +5 halt 7 3150000 +5 halt 8 3140000 +5 halt 9 3150000 +5 halt 10 3150000 +5 halt 11 3140000 +5 halt 12 3140000 +5 halt 13 3140000 +5 halt 14 3140000 +5 halt 15 3140000 +5 halt 16 3150000 +5 halt 17 3150000 +5 halt 18 3160000 +5 halt 19 3140000 +5 halt 20 3140000 +5 halt 21 3150000 +5 halt 22 3150000 +5 halt 23 3150000 +5 halt 24 3150000 +5 halt 25 3150000 +5 halt 26 3160000 +5 halt 27 3140000 +5 halt 28 3150000 +5 halt 29 3150000 +5 halt 30 3150000 +# point pass=5 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997804 +5 run 1 3140000 +5 run 2 3160000 +5 run 3 3140000 +5 run 4 3140000 +5 run 5 3140000 +5 run 6 3160000 +5 run 7 3140000 +5 run 8 3140000 +5 run 9 3150000 +5 run 10 3140000 +5 run 11 3140000 +5 run 12 3210000 +5 run 13 3140000 +5 run 14 3160000 +5 run 15 3160000 +5 run 16 3150000 +5 run 17 3140000 +5 run 18 3140000 +5 run 19 3150000 +5 run 20 3150000 +5 run 21 3150000 +5 run 22 3150000 +5 run 23 3180000 +5 run 24 3140000 +5 run 25 3150000 +5 run 26 3140000 +5 run 27 3150000 +5 run 28 3140000 +5 run 29 3130000 +5 run 30 3140000 +# point pass=5 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997821 +6 halt 1 3150000 +6 halt 2 3160000 +6 halt 3 3150000 +6 halt 4 3140000 +6 halt 5 3150000 +6 halt 6 3140000 +6 halt 7 3140000 +6 halt 8 3140000 +6 halt 9 3140000 +6 halt 10 3140000 +6 halt 11 3150000 +6 halt 12 3140000 +6 halt 13 3140000 +6 halt 14 3140000 +6 halt 15 3140000 +6 halt 16 3140000 +6 halt 17 3140000 +6 halt 18 3130000 +6 halt 19 3140000 +6 halt 20 3140000 +6 halt 21 3140000 +6 halt 22 3140000 +6 halt 23 3160000 +6 halt 24 3150000 +6 halt 25 3150000 +6 halt 26 3150000 +6 halt 27 3140000 +6 halt 28 3140000 +6 halt 29 3150000 +6 halt 30 3140000 +# point pass=6 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997835 +6 run 1 3150000 +6 run 2 3170000 +6 run 3 3140000 +6 run 4 3150000 +6 run 5 3150000 +6 run 6 3150000 +6 run 7 3410000 +6 run 8 3160000 +6 run 9 3150000 +6 run 10 3150000 +6 run 11 3150000 +6 run 12 3280000 +6 run 13 3140000 +6 run 14 3140000 +6 run 15 3150000 +6 run 16 3150000 +6 run 17 3150000 +6 run 18 3140000 +6 run 19 3150000 +6 run 20 3150000 +6 run 21 3140000 +6 run 22 3150000 +6 run 23 3150000 +6 run 24 3140000 +6 run 25 3150000 +6 run 26 3140000 +6 run 27 3140000 +6 run 28 3150000 +6 run 29 3140000 +6 run 30 3140000 +# point pass=6 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997847 +7 run 1 3140000 +7 run 2 3140000 +7 run 3 3150000 +7 run 4 3140000 +7 run 5 3140000 +7 run 6 3140000 +7 run 7 3140000 +7 run 8 3140000 +7 run 9 3140000 +7 run 10 3140000 +7 run 11 3140000 +7 run 12 3140000 +7 run 13 3130000 +7 run 14 3140000 +7 run 15 3140000 +7 run 16 3140000 +7 run 17 3140000 +7 run 18 3140000 +7 run 19 3140000 +7 run 20 3160000 +7 run 21 3140000 +7 run 22 3130000 +7 run 23 3140000 +7 run 24 3140000 +7 run 25 3140000 +7 run 26 3140000 +7 run 27 3140000 +7 run 28 3150000 +7 run 29 3130000 +7 run 30 3140000 +# point pass=7 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997856 +7 halt 1 3140000 +7 halt 2 3140000 +7 halt 3 3230000 +7 halt 4 3140000 +7 halt 5 3130000 +7 halt 6 3130000 +7 halt 7 3140000 +7 halt 8 3150000 +7 halt 9 3140000 +7 halt 10 3140000 +7 halt 11 3130000 +7 halt 12 3130000 +7 halt 13 3140000 +7 halt 14 3130000 +7 halt 15 3140000 +7 halt 16 3150000 +7 halt 17 3150000 +7 halt 18 3130000 +7 halt 19 3140000 +7 halt 20 3140000 +7 halt 21 3130000 +7 halt 22 3130000 +7 halt 23 3120000 +7 halt 24 3130000 +7 halt 25 3160000 +7 halt 26 3130000 +7 halt 27 3130000 +7 halt 28 3130000 +7 halt 29 3130000 +7 halt 30 3130000 +# point pass=7 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997864 +8 halt 1 3140000 +8 halt 2 3130000 +8 halt 3 3140000 +8 halt 4 3140000 +8 halt 5 3150000 +8 halt 6 3140000 +8 halt 7 3140000 +8 halt 8 3140000 +8 halt 9 3140000 +8 halt 10 3130000 +8 halt 11 3140000 +8 halt 12 3140000 +8 halt 13 3160000 +8 halt 14 3150000 +8 halt 15 3140000 +8 halt 16 3150000 +8 halt 17 3140000 +8 halt 18 3150000 +8 halt 19 3140000 +8 halt 20 3150000 +8 halt 21 3140000 +8 halt 22 3150000 +8 halt 23 3140000 +8 halt 24 3140000 +8 halt 25 3130000 +8 halt 26 3130000 +8 halt 27 3140000 +8 halt 28 3140000 +8 halt 29 3140000 +8 halt 30 3140000 +# point pass=8 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997871 +8 run 1 3140000 +8 run 2 3140000 +8 run 3 3130000 +8 run 4 3130000 +8 run 5 3140000 +8 run 6 3140000 +8 run 7 3130000 +8 run 8 3120000 +8 run 9 3120000 +8 run 10 3150000 +8 run 11 3120000 +8 run 12 3140000 +8 run 13 3120000 +8 run 14 3140000 +8 run 15 3130000 +8 run 16 3130000 +8 run 17 3130000 +8 run 18 3130000 +8 run 19 3130000 +8 run 20 3130000 +8 run 21 3140000 +8 run 22 3130000 +8 run 23 3130000 +8 run 24 3120000 +8 run 25 3140000 +8 run 26 3140000 +8 run 27 3140000 +8 run 28 3130000 +8 run 29 3120000 +8 run 30 3150000 +# point pass=8 state=run jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997878 +# done SystemTime { tv_sec: 1793980709, tv_nsec: 798912421 } +# passes used 8 of 8 (dropped: []) +# run - halt = -1.9 +/- 2.7 mW (per-pass sd 7.5); 2/8 passes positive; 6 sample excursions beyond 6 MAD +# per-pass differences (mW): -10.0 0.0 0.0 0.0 -10.0 10.0 5.0 -10.0 +# board power while running 3139 mW; jobs/s 0.0; updates/s 0.0; measured PL clock 99.998 MHz +# pre-registered prediction 0.0 mW: measured/predicted = -inf, -0.7 sigma from it diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/power/t0-recall.dat b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/power/t0-recall.dat new file mode 100644 index 000000000..3a53545fd --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/power/t0-recall.dat @@ -0,0 +1,195 @@ +# class t0-recall cores 1 link AXI +# wai-quantum-landscape-fpga meter passes=3 settle=60s samples=30 null=false sensor=/sys/class/hwmon/hwmon2/power1_input started=SystemTime { tv_sec: 1793978728, tv_nsec: 458975042 } +# pass state sample power_uW +1 halt 1 3150000 +1 halt 2 3160000 +1 halt 3 3140000 +1 halt 4 3150000 +1 halt 5 3150000 +1 halt 6 3150000 +1 halt 7 3140000 +1 halt 8 3140000 +1 halt 9 3150000 +1 halt 10 3150000 +1 halt 11 3140000 +1 halt 12 3150000 +1 halt 13 3170000 +1 halt 14 3160000 +1 halt 15 3150000 +1 halt 16 3140000 +1 halt 17 3150000 +1 halt 18 3150000 +1 halt 19 3150000 +1 halt 20 3170000 +1 halt 21 3150000 +1 halt 22 3150000 +1 halt 23 3140000 +1 halt 24 3140000 +1 halt 25 3140000 +1 halt 26 3140000 +1 halt 27 3140000 +1 halt 28 3150000 +1 halt 29 3140000 +1 halt 30 3150000 +# point pass=1 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99996704 +1 run 1 3330000 +1 run 2 3320000 +1 run 3 3300000 +1 run 4 3300000 +1 run 5 3300000 +1 run 6 3310000 +1 run 7 3300000 +1 run 8 3290000 +1 run 9 3290000 +1 run 10 3330000 +1 run 11 3300000 +1 run 12 3300000 +1 run 13 3300000 +1 run 14 3300000 +1 run 15 3310000 +1 run 16 3300000 +1 run 17 3300000 +1 run 18 3300000 +1 run 19 3290000 +1 run 20 3300000 +1 run 21 3320000 +1 run 22 3290000 +1 run 23 3300000 +1 run 24 3300000 +1 run 25 3320000 +1 run 26 3300000 +1 run 27 3310000 +1 run 28 3300000 +1 run 29 3300000 +1 run 30 3300000 +# point pass=1 state=run jobs_per_s=12499613.3 updates_per_s=0.0 clock_hz=99996898 +2 run 1 3300000 +2 run 2 3300000 +2 run 3 3420000 +2 run 4 3310000 +2 run 5 3310000 +2 run 6 3300000 +2 run 7 3330000 +2 run 8 3300000 +2 run 9 3300000 +2 run 10 3300000 +2 run 11 3310000 +2 run 12 3300000 +2 run 13 3320000 +2 run 14 3310000 +2 run 15 3310000 +2 run 16 3300000 +2 run 17 3300000 +2 run 18 3290000 +2 run 19 3300000 +2 run 20 3300000 +2 run 21 3310000 +2 run 22 3320000 +2 run 23 3300000 +2 run 24 3300000 +2 run 25 3310000 +2 run 26 3300000 +2 run 27 3320000 +2 run 28 3310000 +2 run 29 3310000 +2 run 30 3330000 +# point pass=2 state=run jobs_per_s=12499633.8 updates_per_s=0.0 clock_hz=99997061 +2 halt 1 3140000 +2 halt 2 3130000 +2 halt 3 3140000 +2 halt 4 3150000 +2 halt 5 3140000 +2 halt 6 3140000 +2 halt 7 3140000 +2 halt 8 3140000 +2 halt 9 3140000 +2 halt 10 3130000 +2 halt 11 3140000 +2 halt 12 3130000 +2 halt 13 3140000 +2 halt 14 3140000 +2 halt 15 3130000 +2 halt 16 3140000 +2 halt 17 3140000 +2 halt 18 3150000 +2 halt 19 3150000 +2 halt 20 3130000 +2 halt 21 3140000 +2 halt 22 3140000 +2 halt 23 3130000 +2 halt 24 3140000 +2 halt 25 3140000 +2 halt 26 3140000 +2 halt 27 3150000 +2 halt 28 3140000 +2 halt 29 3140000 +2 halt 30 3130000 +# point pass=2 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997197 +3 run 1 3290000 +3 run 2 3300000 +3 run 3 3300000 +3 run 4 3290000 +3 run 5 3300000 +3 run 6 3300000 +3 run 7 3310000 +3 run 8 3300000 +3 run 9 3300000 +3 run 10 3300000 +3 run 11 3300000 +3 run 12 3300000 +3 run 13 3300000 +3 run 14 3300000 +3 run 15 3310000 +3 run 16 3320000 +3 run 17 3290000 +3 run 18 3300000 +3 run 19 3300000 +3 run 20 3290000 +3 run 21 3310000 +3 run 22 3290000 +3 run 23 3300000 +3 run 24 3310000 +3 run 25 3300000 +3 run 26 3300000 +3 run 27 3290000 +3 run 28 3290000 +3 run 29 3310000 +3 run 30 3300000 +# point pass=3 state=run jobs_per_s=12499665.2 updates_per_s=0.0 clock_hz=99997312 +3 halt 1 3130000 +3 halt 2 3170000 +3 halt 3 3140000 +3 halt 4 3160000 +3 halt 5 3140000 +3 halt 6 3150000 +3 halt 7 3140000 +3 halt 8 3140000 +3 halt 9 3140000 +3 halt 10 3150000 +3 halt 11 3150000 +3 halt 12 3150000 +3 halt 13 3140000 +3 halt 14 3140000 +3 halt 15 3140000 +3 halt 16 3140000 +3 halt 17 3140000 +3 halt 18 3140000 +3 halt 19 3140000 +3 halt 20 3140000 +3 halt 21 3150000 +3 halt 22 3140000 +3 halt 23 3150000 +3 halt 24 3140000 +3 halt 25 3140000 +3 halt 26 3150000 +3 halt 27 3150000 +3 halt 28 3140000 +3 halt 29 3160000 +3 halt 30 3150000 +# point pass=3 state=halt jobs_per_s=0.0 updates_per_s=0.0 clock_hz=99997408 +# done SystemTime { tv_sec: 1793979268, tv_nsec: 808725935 } +# passes used 3 of 3 (dropped: []) +# run - halt = 158.3 +/- 4.4 mW (per-pass sd 7.6); 3/3 passes positive; 1 sample excursions beyond 6 MAD +# per-pass differences (mW): 150.0 165.0 160.0 +# board power while running 3302 mW; jobs/s 12499637.5; updates/s 0.0; measured PL clock 99.997 MHz +# pre-registered prediction 162.0 mW: measured/predicted = 0.98, -0.8 sigma from it diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/throughput-axi.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/throughput-axi.txt new file mode 100644 index 000000000..3356f2af1 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/axi-first/throughput-axi.txt @@ -0,0 +1,2 @@ +settle, dense 64 (recall cue): clock 99.998 MHz, 8.0 clocks a job, 12499763 jobs/s, 0.000e0 cell updates/s, 1.831e11 accumulates/s +sample, sequential, 64 cells, 8 sweeps at T = 0.25: clock 99.998 MHz, 2059.0 clocks a job, 48567 jobs/s, 2.487e7 cell updates/s, 1.138e9 accumulates/s diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/agree-emio.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/agree-emio.txt new file mode 100644 index 000000000..0153ebac5 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/agree-emio.txt @@ -0,0 +1,31 @@ +certified by the host on the conformance set: settle bit-exact, sample bit-exact +parity earned on this board: settle bit-exact, sample bit-exact +the worked examples on the board and on the CPU reference, 200 seeds each + [PASS] talks at its own temperature: 200/200 answers identical + [PASS] talks at temperature 0: 200/200 answers identical + [PASS] clusters at its own temperature: 200/200 answers identical + [PASS] clusters at temperature 0: 200/200 answers identical + [PASS] selection at its own temperature: 200/200 answers identical + [PASS] selection at temperature 0: 200/200 answers identical + [PASS] chain at its own temperature: 200/200 answers identical + [PASS] chain at temperature 0: 200/200 answers identical + [PASS] assign-2x2 at its own temperature: 200/200 answers identical + [PASS] assign-2x2 at temperature 0: 200/200 answers identical + [PASS] blocks at its own temperature: 200/200 answers identical + [PASS] blocks at temperature 0: 200/200 answers identical + [PASS] assign-3x3 at its own temperature: 200/200 answers identical + [PASS] assign-3x3 at temperature 0: 200/200 answers identical + [PASS] colour-six at its own temperature: 200/200 answers identical + [PASS] colour-six at temperature 0: 200/200 answers identical + [PASS] colour-four at its own temperature: 200/200 answers identical + [PASS] colour-four at temperature 0: 200/200 answers identical + [PASS] piles at its own temperature: 200/200 answers identical + [PASS] piles at temperature 0: 200/200 answers identical + [PASS] cold-constraint at its own temperature: 200/200 answers identical + [PASS] cold-constraint at temperature 0: 200/200 answers identical + [PASS] cold-start at its own temperature: 200/200 answers identical + [PASS] cold-start at temperature 0: 200/200 answers identical + [PASS] too-costly at its own temperature: 200/200 answers identical + [PASS] too-costly at temperature 0: 200/200 answers identical + [PASS] typo: refused by the reader before any member runs: Line 6: `oppos` is not a statement epuML knows. Did you mean `oppose`? +27 checks, 0 failed diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/conform-emio.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/conform-emio.txt new file mode 100644 index 000000000..e7903e3ca --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/conform-emio.txt @@ -0,0 +1,48 @@ +wai.quantum.landscape/1 member tests (quantum-conformance/landscape/vectors.json) +settle cases 13/13 bit for bit; chains 42/42 bit for bit + [PASS] settle kernel: the settle cases, bit for bit (state, sweeps, ending, accumulates), each held to the job + [PASS] sample kernel: the chain vectors, bit for bit (state, sweeps, ending, accumulates, draws), each held to the job + [PASS] cells: 650/650 cut points the host computes for the engine equal the set's, bit for bit + [PASS] cell h=-40 c=0 T=0.25: 100000 draws, counts [100000, 0, 0], z +0.00 +0.00 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-40 c=2 T=1: 100000 draws, counts [100000, 0, 0], z +0.00 +0.00 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-40 c=8 T=2.9: 100000 draws, counts [100000, 0, 0], z +1.27 -1.27 -0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-17 c=1 T=25: 100000 draws, counts [55805, 29689, 14506], z -1.59 +0.91 +1.07 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-17 c=8 T=0.7: 100000 draws, counts [99999, 1, 0], z -1.45 +1.45 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-5 c=1 T=1.8708286933869707: 100000 draws, counts [89058, 10527, 415], z -0.17 +0.28 -0.48 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-5 c=3 T=13.7: 100000 draws, counts [42641, 36802, 20557], z +0.11 -0.21 +0.12 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-3 c=1 T=0.3: 100000 draws, counts [99874, 126, 0], z +0.10 -0.10 -0.01 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-3 c=3 T=1.3: 100000 draws, counts [50015, 49537, 448], z +1.65 -1.37 -2.01 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-1 c=0 T=7.1: 100000 draws, counts [38122, 33245, 28633], z -0.00 +0.88 -0.91 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=-1 c=3 T=0.25: 100000 draws, counts [32, 99968, 0], z -0.27 +0.27 -0.11 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=0 c=0 T=1: 100000 draws, counts [33342, 33254, 33404], z +0.06 -0.53 +0.47 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=0 c=2 T=2.9: 100000 draws, counts [24786, 50120, 25094], z -1.88 +1.31 +0.37 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=0 c=8 T=25: 100000 draws, counts [29353, 41113, 29534], z -1.79 +2.16 -0.53 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=1 c=2 T=0.7: 100000 draws, counts [1130, 79767, 19103], z +0.97 -0.12 -0.14 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=1 c=8 T=1.8708286933869707: 100000 draws, counts [801, 96844, 2355], z +0.43 -1.26 +1.20 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=2 c=1 T=13.7: 100000 draws, counts [27835, 34700, 37465], z -0.48 -0.22 +0.66 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=2 c=8 T=0.3: 100000 draws, counts [0, 100000, 0], z -0.00 +0.01 -0.01 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=3 c=1 T=1.3: 100000 draws, counts [835, 17462, 81703], z +0.94 -0.59 +0.36 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=3 c=3 T=7.1: 100000 draws, counts [17600, 41216, 41184], z -0.66 +0.36 +0.15 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=5 c=1 T=0.25: 100000 draws, counts [0, 0, 100000], z +0.00 -0.11 +0.11 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=5 c=3 T=1: 100000 draws, counts [9, 12006, 87985], z +2.50 +0.84 -0.89 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=9 c=0 T=2.9: 100000 draws, counts [204, 4140, 95656], z +0.83 -2.31 +2.09 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=9 c=2 T=25: 100000 draws, counts [21552, 33829, 44619], z -1.18 +0.85 +0.17 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=17 c=0 T=0.7: 100000 draws, counts [0, 0, 100000], z +0.00 -0.00 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=17 c=2 T=1.8708286933869707: 100000 draws, counts [0, 36, 99964], z -0.04 +0.53 -0.53 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=17 c=8 T=13.7: 100000 draws, counts [5263, 32393, 62344], z +0.64 +0.21 -0.50 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=40 c=2 T=0.3: 100000 draws, counts [0, 0, 100000], z +0.00 +0.00 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] cell h=40 c=8 T=1.3: 100000 draws, counts [0, 0, 100000], z +0.00 -0.00 +0.00 (limit 4); 100000/100000 equal to the reference's draw + [PASS] battery one-pair: chi-square 10.7 on 8 degrees of freedom (critical 28.0) + [PASS] battery a-pair-apart: chi-square 45.3 on 26 degrees of freedom (critical 62.1) + [PASS] battery a-pair-far-apart: chi-square 75.3 on 79 degrees of freedom (critical 141.8) + [PASS] battery a-triangle: chi-square 31.0 on 26 degrees of freedom (critical 62.1) + [PASS] battery wide-fields-and-cost: chi-square 18.3 on 22 degrees of freedom (critical 55.2) + [PASS] battery a-small-field-cold: chi-square 6.9 on 4 degrees of freedom (critical 18.1) + [PASS] battery ending-at-zero: chi-square 17.1 on 11 degrees of freedom (critical 34.5) + [PASS] battery cooling: chi-square 16.8 on 23 degrees of freedom (critical 56.9) + [PASS] the all-at-once control (every cell moved together) fails the battery on ["one-pair", "a-pair-apart", "a-pair-far-apart", "a-triangle", "a-small-field-cold", "ending-at-zero", "cooling"] + [PASS] jobs: 31/31 requests answer as the set says (state, energy, verdict, mode, restarts, sweeps, accumulates, draws, replay digest, or the refusal) + [PASS] 2000/2000 random workloads (2-64 cells, fields -5..5, cost 0-3) settle exactly as the reference does + [PASS] 4096/4096 fields read back equal W s + b (dense W, fields across the engine's ±524287) + [PASS] the engine still answers with its ID at the end: no other session replaced it +44 checks, 0 failed diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/latency-emio.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/latency-emio.txt new file mode 100644 index 000000000..a7a6036a3 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/latency-emio.txt @@ -0,0 +1,7 @@ +processor-to-logic latency, EMIO link: one board, the engine at its measured clock + timer: CNTVCT_EL0 at 99.999 MHz, one tick 10.0 ns + the timer alone (two reads, an ISB each) median 10 ns p10 10 p90 20 p99 20 max 12120 mean 12 (n 20000) + EMIO register read (handshake over GPIO) median 930 ns p10 920 p90 930 p99 940 max 104301 mean 954 (n 5000) + EMIO register write (handshake over GPIO) median 790 ns p10 780 p90 800 p99 800 max 30540 mean 808 (n 5000) + EMIO, plain register accesses: a full table (64 levels x 128 fields, 32835 register writes): median 29.00 ms of 6, 883 ns a write + EMIO, plain register accesses: 4096 chains of 8 cells x 4 sweeps: 0.060 s, 14.61 us a chain, 68443 chains/s diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/ps-ports.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/ps-ports.txt new file mode 100644 index 000000000..aa89c808f --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/ps-ports.txt @@ -0,0 +1 @@ +FPD_SLCR AFI_FS 0x00000a00: M_AXI_HPM0_FPD 128 bits; AFIFM2 RDCTRL 0x000003b0 WRCTRL 0x000003b0: S_AXI_HP0_FPD reads 128 bits, writes 128 bits (the AXI build is made for 128 on the master and for reads): they match diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/throughput-emio.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/throughput-emio.txt new file mode 100644 index 000000000..b5da71218 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-checks/throughput-emio.txt @@ -0,0 +1,2 @@ +settle, dense 64 (recall cue): clock 99.998 MHz, 8.0 clocks a job, 12499778 jobs/s, 0.000e0 cell updates/s, 1.831e11 accumulates/s +sample, sequential, 64 cells, 8 sweeps at T = 0.25: clock 99.998 MHz, 2059.0 clocks a job, 48566 jobs/s, 2.487e7 cell updates/s, 1.138e9 accumulates/s diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/campaign.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/campaign.txt new file mode 100644 index 000000000..924a2294e --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/emio-classes/campaign.txt @@ -0,0 +1,11 @@ +2026-11-06T12:38:56Z start; engine answers: ID and version 0x00404002; measured clock 99.997 MHz +2026-11-06T12:48:56Z step done +2026-11-06T12:48:56Z known answer skipped: KNOWN_ANSWER and KNOWN_BITSTREAM not set +2026-11-06T13:12:57Z null done exit=0 +2026-11-06T13:42:58Z t0-recall done exit=0 +2026-11-06T14:13:00Z gibbs-recall done exit=0 +2026-11-06T14:37:01Z t0-idle done exit=0 +PL0_REF_CTRL was 0x1010f00; nominal 50.00 MHz; measured 49.9989 MHz +2026-11-06T14:55:03Z 50 MHz done exit=0 +PL0_REF_CTRL was 0x1011e00; nominal 100.00 MHz; measured 99.9981 MHz +2026-11-06T14:55:04Z end diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/first-attempt/campaign.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/first-attempt/campaign.txt new file mode 100644 index 000000000..843938b08 --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/first-attempt/campaign.txt @@ -0,0 +1,6 @@ +2026-11-06T08:01:41Z start; engine answers: ID and version 0x00404002; measured clock 99.997 MHz +2026-11-06T08:11:42Z step done +2026-11-06T08:35:54Z known answer done exit=0 +wai-quantum-fpga: another session is active on the board, not loading: +1670130 sudo KNOWN_ANSWER=/home/ubuntu/kv_runhalt.sh KNOWN_BITSTREAM=ev_rdet.bit.bin setsid nohup sh power_campaign.sh campaign-v2 +2026-11-06T08:35:55Z reload of the engine failed -- 2.54.0 (Apple Git-157) From d4867b1b830802631dc2ba1731ee6a596fe20e31 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 7 Nov 2026 03:22:22 -0500 Subject: [PATCH 10/11] wai-quantum-landscape-fpga: the review of #677 answered The lock and reloads. load takes the board lock before it reconfigures, and the session check sees other runs of this tool and its scripts (their own parents excepted), so a reload waits for the job that holds the board. The scripts do not hold the lock across the steps they run. A reload of the same design, which passes the signature and the ID, is seen by the engine's uptime counter and its reset BOUND register: the engine checks both before it takes a workload or a table as loaded and after every job, and refuses a result read across a reload. A batch that comes back short, and a sampled chain whose sweep count is wrong, are refused. The AXI port. It checks the port's write width as well as its read width; it fills a result page with the illegal cell pair before a write-back and refuses a result still holding it; it refuses a transfer whose page moved. It is written against two traits, so a software link tests it off the board: the guard, the DMA, the sequencer's count, the write-back's layout, dropped accesses. Tests. lowered_settle's five cases and 300 random workloads with fields to 2^40 against the reference; the joint test counts against the chains requested and checks the control's cell family. CI gates on the change, requires tests to run, denies clippy warnings, certifies the model member, runs a job on it, builds on linux-arm64, and replays both testbenches in Icarus Verilog (scripts/run_tb.sh). The names check reads .v, .tcl, .xdc and .dat files. The build. The tool version and each block's version are enforced; the multi-core build refuses the AXI link. The documents. The latency table says how each row was timed; the engine's share of a chain is its own clock count; the sampling energy carries its error and its floor; the known answer is placed in time; the step response is reported; derived figures are marked; the scope of the meter, the guard's window and the DMA's locked pages are stated; provenance says what the private code declared; old names are gone; the build and simulator results have a raw record. Co-Authored-By: Claude Opus 5.5 --- .../workflows/wai-quantum-landscape-fpga.yml | 126 +- scripts/check-public-names.sh | 6 +- .../2026-11-06-landscape-fpga-member.md | 2 + wai/wai-quantum-landscape-fpga/PROVENANCE.md | 16 +- wai/wai-quantum-landscape-fpga/README.md | 131 ++- wai/wai-quantum-landscape-fpga/landscape.tcl | 30 +- .../measurements/2026-11-05/README.md | 2 +- .../measurements/2026-11-06/README.md | 4 +- .../measurements/2026-11-06/build.txt | 33 + .../rtl/landscape.xdc | 2 +- .../scripts/build_bitstream.sh | 11 +- .../scripts/power_campaign.sh | 5 +- .../scripts/run_all.sh | 36 +- .../scripts/run_tb.sh | 25 + .../scripts/step_settle.py | 20 +- wai/wai-quantum-landscape-fpga/src/axi.rs | 1038 ++++++++++++----- wai/wai-quantum-landscape-fpga/src/board.rs | 96 +- wai/wai-quantum-landscape-fpga/src/conform.rs | 2 +- wai/wai-quantum-landscape-fpga/src/engine.rs | 184 ++- wai/wai-quantum-landscape-fpga/src/exact.rs | 40 +- wai/wai-quantum-landscape-fpga/src/latency.rs | 28 +- wai/wai-quantum-landscape-fpga/src/lib.rs | 11 +- wai/wai-quantum-landscape-fpga/src/main.rs | 97 +- wai/wai-quantum-landscape-fpga/src/member.rs | 124 ++ wai/wai-quantum-landscape-fpga/src/power.rs | 12 +- .../tb/tb_landscape.v | 2 +- 26 files changed, 1571 insertions(+), 512 deletions(-) create mode 100644 wai/wai-quantum-landscape-fpga/measurements/2026-11-06/build.txt create mode 100755 wai/wai-quantum-landscape-fpga/scripts/run_tb.sh diff --git a/.github/workflows/wai-quantum-landscape-fpga.yml b/.github/workflows/wai-quantum-landscape-fpga.yml index 335e10d05..6e2f6a463 100644 --- a/.github/workflows/wai-quantum-landscape-fpga.yml +++ b/.github/workflows/wai-quantum-landscape-fpga.yml @@ -1,9 +1,15 @@ name: wai-quantum-landscape-fpga reference design -# The FPGA board's member of energy-landscape jobs (wai/wai-quantum-landscape-fpga): its unit tests, and every -# check of the conformance set, every worked example and the joint-statistics test run against the -# register-level model of the logic (src/model.rs), which the logic's testbenches are held to. No board and no vendor tool here: the -# board's own runs are in the crate's measurements and README. +# The FPGA board's member of energy-landscape jobs (wai/wai-quantum-landscape-fpga): its unit tests and +# clippy, warnings as errors; the member certified on the model of the logic (src/model.rs) and every +# check of the conformance set, every worked example and the joint-statistics test run on it; the +# board's code built and tested on the board's processor's architecture (aarch64, linux-arm64), +# where its cache maintenance compiles; +# and the logic itself: both testbenches replay the model's session against the RTL in an +# open-source simulator, every read checked. No board and no vendor tool here: the board's own runs +# are in the crate's measurements and README. +# +# Push filters match the push's delta, so every path the jobs read is listed. concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -17,6 +23,7 @@ on: - 'wai/wai-quantum/src/quantum_landscape/**' - 'wai/wai-quantum/src/quantum_landscape.rs' - 'wai/wai-quantum/src/repro.rs' + - 'wai/wai-quantum/Cargo.toml' - 'wai/quantum-conformance/landscape/**' - 'rust-toolchain.toml' - '.github/workflows/wai-quantum-landscape-fpga.yml' @@ -26,15 +33,91 @@ on: - 'wai/wai-quantum/src/quantum_landscape/**' - 'wai/wai-quantum/src/quantum_landscape.rs' - 'wai/wai-quantum/src/repro.rs' + - 'wai/wai-quantum/Cargo.toml' - 'wai/quantum-conformance/landscape/**' - 'rust-toolchain.toml' - '.github/workflows/wai-quantum-landscape-fpga.yml' jobs: + relevant: + name: does this change touch it + runs-on: gate + outputs: + run: ${{ steps.gate.outputs.run }} + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + - id: gate + run: | + set -euo pipefail + r=$(python3 scripts/pr-touches-paths.py .github/workflows/wai-quantum-landscape-fpga.yml \ + "${{ github.event.pull_request.base.sha }}" \ + "${{ github.event.pull_request.head.sha }}") + echo "this change touches wai-quantum-landscape-fpga: $r" + echo "run=$r" >> "$GITHUB_OUTPUT" + model: - name: the member against the model of the logic · linux, release + name: the member against the model of the logic, and the logic against the model · linux, release + needs: relevant + if: needs.relevant.outputs.run == 'true' runs-on: linux timeout-minutes: 60 + steps: + - uses: actions/checkout@v5 + - name: Install Rust 1.99.1 and the simulator + shell: bash + run: | + export PATH="$HOME/.cargo/bin:$PATH" + command -v rustup >/dev/null 2>&1 || \ + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.99.1 + rustup toolchain install 1.99.1 --profile minimal --component clippy + rustup default 1.99.1 + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo" + command -v iverilog >/dev/null || { $SUDO apt-get update -qq; $SUDO apt-get install -y -qq iverilog; } + - name: Unit tests and clippy + shell: bash + working-directory: wai/wai-quantum-landscape-fpga + run: | + set -euo pipefail + cargo test --locked --release 2>&1 | tee "$RUNNER_TEMP/test.log" + grep -qE '^test result: ok\. [1-9][0-9]* passed; 0 failed' "$RUNNER_TEMP/test.log" || { echo "::error::no test ran"; exit 1; } + cargo clippy --locked --release --all-targets -- -D warnings + - name: The member certified, the conformance set and the worked examples, on the model + shell: bash + working-directory: wai/wai-quantum-landscape-fpga + run: | + set -euo pipefail + cargo run --locked --release -- selftest --scale 0.05 --seeds 5 | tee "$RUNNER_TEMP/selftest.log" + grep -q 'certified by the host on the conformance set: settle bit-exact, sample bit-exact' "$RUNNER_TEMP/selftest.log" + grep -qE '^[0-9]+ checks, 0 failed$' "$RUNNER_TEMP/selftest.log" + - name: A job on the model, certified and sealed + shell: bash + working-directory: wai/wai-quantum-landscape-fpga + run: | + set -euo pipefail + printf '{"program": "vars 2\\noppose 0 1\\nsolve\\n", "seed": "7"}' | cargo run --locked --release -- landscape run - --model --demo-key > "$RUNNER_TEMP/job.json" + grep -q '"parity": "bit-exact"' "$RUNNER_TEMP/job.json" + grep -q '"id": "fpga:model-landscape-64"' "$RUNNER_TEMP/job.json" + - name: Joint statistics against exact enumeration, on the model + shell: bash + working-directory: wai/wai-quantum-landscape-fpga + run: | + set -euo pipefail + cargo run --locked --release -- joint --model --scale 0.05 | tee "$RUNNER_TEMP/joint.log" + grep -qE '^[0-9]+ checks, 0 failed$' "$RUNNER_TEMP/joint.log" + - name: The logic against the model, both testbenches + shell: bash + working-directory: wai/wai-quantum-landscape-fpga + run: sh scripts/run_tb.sh both + + board-target: + name: the board's code on the board's processor's architecture · linux-arm64, release + needs: relevant + if: needs.relevant.outputs.run == 'true' + runs-on: linux-arm64 + timeout-minutes: 60 steps: - uses: actions/checkout@v5 - name: Install Rust 1.99.1 @@ -43,35 +126,16 @@ jobs: export PATH="$HOME/.cargo/bin:$PATH" command -v rustup >/dev/null 2>&1 || \ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.99.1 - rustup toolchain install 1.99.1 --profile minimal + rustup toolchain install 1.99.1 --profile minimal --component clippy rustup default 1.99.1 echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - - name: Unit tests - shell: bash - working-directory: wai/wai-quantum-landscape-fpga - run: cargo test --locked --release - - name: The conformance set and the worked examples, on the model + # The cache maintenance the DMA needs (dc cvac, dc civac, dsb) compiles only for aarch64. + - name: Unit tests and clippy shell: bash working-directory: wai/wai-quantum-landscape-fpga run: | set -euo pipefail - cargo run --locked --release -- selftest --scale 0.05 --seeds 5 | tee "$RUNNER_TEMP/selftest.log" - grep -q 'sample kernel: the chain vectors, bit for bit' "$RUNNER_TEMP/selftest.log" - grep -qE '^[0-9]+ checks, 0 failed$' "$RUNNER_TEMP/selftest.log" - - name: Joint statistics against exact enumeration, on the model - shell: bash - working-directory: wai/wai-quantum-landscape-fpga - run: | - set -euo pipefail - cargo run --locked --release -- joint --model --scale 0.05 | tee "$RUNNER_TEMP/joint.log" - grep -qE '^[0-9]+ checks, 0 failed$' "$RUNNER_TEMP/joint.log" - - name: The testbenches' scripts record from the model - shell: bash - working-directory: wai/wai-quantum-landscape-fpga - run: | - set -euo pipefail - cargo run --locked --release -- gen-tb "$RUNNER_TEMP/landscape_tb.script" - test "$(grep -c '^R ' "$RUNNER_TEMP/landscape_tb.script")" -gt 200 - cargo run --locked --release -- gen-tb "$RUNNER_TEMP/landscape_axi_tb.script" --axi - test "$(grep -c '^Q ' "$RUNNER_TEMP/landscape_axi_tb.script")" -gt 5 - test "$(grep -c '^D ' "$RUNNER_TEMP/landscape_axi_tb.script")" -gt 10 + cargo test --locked --release 2>&1 | tee "$RUNNER_TEMP/test.log" + grep -qE '^test result: ok\. [1-9][0-9]* passed; 0 failed' "$RUNNER_TEMP/test.log" || { echo "::error::no test ran"; exit 1; } + cargo clippy --locked --release --all-targets -- -D warnings + diff --git a/scripts/check-public-names.sh b/scripts/check-public-names.sh index 7415e6936..adee4c89c 100755 --- a/scripts/check-public-names.sh +++ b/scripts/check-public-names.sh @@ -31,7 +31,9 @@ # CI runs it so on every change, a pull request included. # # Scope: the tracked files under wai/ with the extensions .md .rs .toml .py -# .sh .mjs .js .json .html .css .ttl .h .c .txt, and wai/'s .gitignore files; +# .sh .mjs .js .json .html .css .ttl .h .c .txt .wgsl, the logic's sources and +# build files (.v .tcl .xdc) and the measurements' logs (.dat), and wai/'s +# .gitignore files; # or, when files are given as arguments, those files (the self-test, # scripts/test-public-text-checks.sh, uses this), each a path relative to the # directory the script is run from, or absolute. No file is excluded: the @@ -116,7 +118,7 @@ else files+=("$f") done < <(git ls-files 'wai/*.md' 'wai/*.rs' 'wai/*.toml' 'wai/*.py' 'wai/*.sh' 'wai/*.mjs' 'wai/*.js' \ 'wai/*.json' 'wai/*.html' 'wai/*.css' 'wai/*.ttl' 'wai/*.h' 'wai/*.c' 'wai/*.txt' \ - 'wai/*.gitignore' 'wai/*.wgsl') + 'wai/*.gitignore' 'wai/*.wgsl' 'wai/*.v' 'wai/*.tcl' 'wai/*.xdc' 'wai/*.dat') fi if [ ${#files[@]} -eq 0 ]; then echo "::error::no file to check" >&2 diff --git a/wai/changes/2026-11-06-landscape-fpga-member.md b/wai/changes/2026-11-06-landscape-fpga-member.md index 3f72611f7..5b4a264cd 100644 --- a/wai/changes/2026-11-06-landscape-fpga-member.md +++ b/wai/changes/2026-11-06-landscape-fpga-member.md @@ -5,5 +5,7 @@ - **An AXI link** (`rtl/landscape_axi.v`) replaces the EMIO register port as the default build: the engine's registers on AXI-Lite, bulk loads read from DDR by a write-list DMA, a sequencer that runs a job's chains in fabric, start to result, and a write-back that puts the results in DDR. The link answers every access within a few clocks whatever the engine is doing, and the host reads a signature over EMIO before every AXI access, so a design replaced by another session is caught before an access could go unanswered. A second testbench drives the link and its DMA against the model. The processor-to-logic latency is measured on the board (`latency`). - **The sample kernel is held to its exact joint law** (`joint`, `src/exact.rs`): small workloads enumerated over every state, chains on the board against that law by chi-square and G over all states, every cell's and every pair's frequencies, and a control with the right marginals and independent cells that must be rejected. - **The member is certified by the host** on the conformance set and the statistical battery, runs any schedule of temperatures the job gives, and maps fields and costs beyond its registers exactly for sweeps at 0. +- **One session at a time, and a reload seen:** every board command, `load` included, takes the board's lock first, and the session check sees other runs of this tool. A reload of the same design under a job, which passes the signature and the engine's ID, is seen by the engine's uptime counter and its reset registers, and the job's result is refused instead of kept. +- **CI** builds and tests the host on x86-64 and on aarch64, the board's architecture, certifies the member on the model, and replays both testbenches against the logic in an open-source simulator. `scripts/check-public-names.sh` now also reads `.v`, `.tcl`, `.xdc` and `.dat` files. - **The measurements** are board-level, one board and one meter, each campaign with its prediction committed first (`measurements/`). No bitstream is distributed: the vendor tool's licence limits a bitstream to that vendor's devices, so the README gives the build steps. - The design came from a private repository and is released under `Apache-2.0` by its copyright holder (`wai-quantum-landscape-fpga/PROVENANCE.md`). diff --git a/wai/wai-quantum-landscape-fpga/PROVENANCE.md b/wai/wai-quantum-landscape-fpga/PROVENANCE.md index 25502958e..4772b67fd 100644 --- a/wai/wai-quantum-landscape-fpga/PROVENANCE.md +++ b/wai/wai-quantum-landscape-fpga/PROVENANCE.md @@ -2,10 +2,13 @@ The engine's logic, its register-level model, the host and the power-measurement scripts were written in a private repository, where the first version of the design was built and measured on -the board, at commit `24bb5c8e8032e95a614a9db1e6f2e98ac7ea1233`. They are released here under this -repository's `Apache-2.0`, by the copyright holder's decision of 2026-11-05. This repository's -`LICENSE` governs everything under this directory; the files carry -`SPDX-License-Identifier: Apache-2.0`. +the board, at commit `24bb5c8e8032e95a614a9db1e6f2e98ac7ea1233`. There the host crate's manifest +declared `CC0-1.0`, and the logic, the build script and the shell scripts carried no licence. They +are released here under this repository's `Apache-2.0`, by their copyright holder's decision of +2026-11-05. This repository's `LICENSE` governs everything under this directory. The logic, the +testbenches, the build script, the constraints and the scripts carry +`SPDX-License-Identifier: Apache-2.0`; the Rust sources, as elsewhere in this repository, carry no +header and are covered by `LICENSE` and by `license = "Apache-2.0"` in `Cargo.toml`. ## What changed from that code @@ -31,6 +34,9 @@ repository's `Apache-2.0`, by the copyright holder's decision of 2026-11-05. Thi ## What is not here -No bitstream (README, "Build and load"). The first version's raw power logs are in +No bitstream (README, "Build and load"). Nor the known-answer vehicle the power campaigns checked +the meter with: a design of the maintainers' own from 2026-09 and the script that runs it, both in +the private repository. A builder uses a known answer of their own (`KNOWN_ANSWER` in +`scripts/power_campaign.sh`) or none; the null and the clock-scaling check need nothing else. The first version's raw power logs are in `measurements/2026-11-05/` as they were written, under that version's tool name; its predictions are the files committed before that measurement, copied unchanged. diff --git a/wai/wai-quantum-landscape-fpga/README.md b/wai/wai-quantum-landscape-fpga/README.md index 580c1dd86..356fa49f5 100644 --- a/wai/wai-quantum-landscape-fpga/README.md +++ b/wai/wai-quantum-landscape-fpga/README.md @@ -16,7 +16,7 @@ counter-based generator, and nothing here is a quantum effect. Every answer says | 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`), whole board, 10 mW steps | +| 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. | @@ -72,7 +72,8 @@ couplings, at most 63), with two thresholds, `c − b` and `−c − b`; only wh `[−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, -holds it to that). Sampling with such fields is refused. +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. @@ -82,7 +83,7 @@ beyond ±524,287 or a cost beyond 1,048,575; a job whose uncertain fields span m **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 pinned +**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 @@ -94,9 +95,10 @@ and copies its state, sweeps and status into the result RAM, chain after chain, 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 cleans and invalidates the page before the write-back and invalidates -it after, because the port is not coherent. An error is reported, and the write-back's done count -proves it ran. +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 @@ -106,6 +108,15 @@ replaced design is caught before an AXI access is made. And the link answers eve 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. @@ -156,7 +167,7 @@ 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 +./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 - @@ -169,19 +180,22 @@ 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 -- gen-tb s.txt [--axi] # a testbench's script, recorded from 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 that script against the logic and compare every read with -the model's. `tb/tb_landscape.v` goes through the EMIO port and its synchroniser. +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. In the vendor's simulator (xsim 2026.1): the EMIO script, -40,458 lines and 530 checked reads, and the AXI script, 40,800 lines, 76 write lists, 22 batches and -852 checked values, both with 0 mismatches; a flipped expectation is reported. An open-source -simulator was not tried. +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, 40,785 lines and 856 checked +reads, and the AXI script, 41,127 lines, 76 write lists, 22 batches and 1,178 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. @@ -197,14 +211,21 @@ simulator was not tried. - **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, a null (halt against halt), the same - job at half the clock (dynamic power must halve), and a known-answer vehicle where one exists. +- **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 -(about 3.1 W idle), so the power figures are differences of a few percent of the total. +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 @@ -247,29 +268,38 @@ 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), 100,000 times -(5,000 for EMIO); medians, with the 99th percentile where it differs. +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 | median | -|---|---| -| EMIO register read, handshake over GPIO (EMIO build) | 930 ns | -| EMIO register write, handshake over GPIO (EMIO build) | 790 ns | -| a GPIO register read (the AXI link's signature) | 230 ns | -| AXI-Lite read, a link register / an engine register | 230 ns / 240 ns | -| AXI-Lite write, to its response (store, then DSB) | 250 ns (p99 380) | -| AXI-Lite writes back to back, posted | 51 ns each | -| a guarded read, as every job access goes (signature, then read) | 470 ns | -| a write list by DMA: 1 entry, kick to done / 512 entries (one page) | 7.6 µs / 20.3 µs, 39.7 ns an entry | -| a batch's results by DMA: 128 chains (one page), kick to read | 37.6 µs, 293 ns a chain | +| 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 | -End to end: +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 | 9.4 µs a chain | 4.34 µs a chain (230,000 a second) | +| 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) | -The engine itself spends about 1.6 µs on such a chain; the rest is the host's. +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 @@ -280,12 +310,12 @@ 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 nJ a cell draw | 12 (4 to 30) | within; see the null | +| 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) | its own | 73.8 ± 10.3 mW (8 passes) | 61.7 ± 6.5, within 2 combined σ | 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 | @@ -294,8 +324,16 @@ The board drew about 3.14 W halted and 3.30 W running the settle job. **One of t 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`). During the class -campaign a status read over SSH touched the board once, at about 13:25 UTC, during the settle class. +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 @@ -320,9 +358,15 @@ with its couplings fixed in the logic. 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, about a - microsecond later; a reload by another session landing inside that window would still hang the - board. It has not been seen; it is not excluded. +- **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. @@ -333,6 +377,11 @@ with its couplings fixed in the logic. - **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 @@ -344,7 +393,7 @@ with its couplings fixed in the logic. | `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 | +| `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 | diff --git a/wai/wai-quantum-landscape-fpga/landscape.tcl b/wai/wai-quantum-landscape-fpga/landscape.tcl index 13fd4fce8..d03854dbb 100644 --- a/wai/wai-quantum-landscape-fpga/landscape.tcl +++ b/wai/wai-quantum-landscape-fpga/landscape.tcl @@ -2,10 +2,13 @@ # Bitstream build for the energy-landscape engine. Vivado batch mode: # vivado -mode batch -source landscape.tcl -tclargs [FREQ ] [TAG ] [JOBS ] [CORES ] [LINK emio|axi] # The part and board are data: PART and BOARD below. There is no programmable-logic I/O: the -# processing system provides the clock and the link. LINK emio (the default) carries register -# accesses over the EMIO GPIO lines; LINK axi gives an AXI-Lite register port, a write-list DMA -# from DDR and an in-fabric chain sequencer (rtl/landscape_axi.v). Every block below is the -# vendor's no-charge processing-system configuration or AXI infrastructure; no licensed IP. +# processing system provides the clock and the link. LINK axi (the default on one engine) gives an +# AXI-Lite register port, a write-list DMA from DDR and an in-fabric chain sequencer +# (rtl/landscape_axi.v); LINK emio carries register accesses over the EMIO GPIO lines, and is the +# only link of the multi-core build. Every block below is the vendor's no-charge processing-system +# configuration or AXI infrastructure; no licensed IP. The tool and block versions are the ones this +# design was built and measured with, and a different tool stops the build: set ANY_TOOL to go on, +# and expect a different bitstream. set part xck26-sfvc784-2LV-c set board xilinx.com:kv260_som:part0:2.0 proc arg {name dflt} { @@ -18,14 +21,25 @@ set freq [arg FREQ 100] set tag [arg TAG landscape] set jobs [arg JOBS 4] set cores [arg CORES 1] -set link [arg LINK emio] +set link [arg LINK [expr {$cores > 1 ? "emio" : "axi"}]] +if {$cores > 1 && $link ne "emio"} { error "the multi-core build has the EMIO link only" } +set tool 2026.1 +if {[version -short] ne $tool && ![info exists ::env(ANY_TOOL)]} { + error "this design was built and measured with Vivado $tool, not [version -short]: set ANY_TOOL to build anyway" +} +# Each block by its exact version. +proc block {name version} { + set d [get_ipdefs -all xilinx.com:ip:$name:$version] + if {$d eq ""} { error "no $name $version in this tool" } + return [lindex $d 0] +} set here [file dirname [file normalize [info script]]] puts "BUILD: tag=$tag freq=$freq jobs=$jobs cores=$cores link=$link here=$here" create_project ev_$tag ./ev_$tag -part $part -force set_property board_part $board [current_project] create_bd_design sys -create_bd_cell -type ip -vlnv [lindex [get_ipdefs *zynq_ultra_ps_e*] end] ps +create_bd_cell -type ip -vlnv [block zynq_ultra_ps_e 3.5] ps apply_bd_automation -rule xilinx.com:bd_rule:zynq_ultra_ps_e -config {apply_board_preset 1} [get_bd_cells ps] set_property -dict [list \ CONFIG.PSU__FPGA_PL0_ENABLE {1} \ @@ -51,12 +65,12 @@ if {$link eq "axi"} { connect_bd_net [get_bd_pins ps/pl_clk0] [get_bd_pins ps/maxihpm0_fpd_aclk] connect_bd_net [get_bd_pins ps/pl_clk0] [get_bd_pins ps/saxihp0_fpd_aclk] create_bd_port -dir I -type rst aresetn - set dw [lindex [get_ipdefs *axi_dwidth_converter*] end] + set dw [block axi_dwidth_converter 2.1] create_bd_cell -type ip -vlnv $dw down create_bd_cell -type ip -vlnv $dw up set_property -dict [list CONFIG.SI_DATA_WIDTH {128} CONFIG.MI_DATA_WIDTH {32}] [get_bd_cells down] set_property -dict [list CONFIG.SI_DATA_WIDTH {64} CONFIG.MI_DATA_WIDTH {128}] [get_bd_cells up] - create_bd_cell -type ip -vlnv [lindex [get_ipdefs *axi_protocol_converter*] end] conv + create_bd_cell -type ip -vlnv [block axi_protocol_converter 2.1] conv set_property -dict [list CONFIG.SI_PROTOCOL {AXI4} CONFIG.MI_PROTOCOL {AXI4LITE} CONFIG.DATA_WIDTH {32}] [get_bd_cells conv] foreach c {down up} { connect_bd_net [get_bd_pins ps/pl_clk0] [get_bd_pins $c/s_axi_aclk] diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/README.md b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/README.md index 35a602c14..660627526 100644 --- a/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/README.md +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-05/README.md @@ -13,4 +13,4 @@ predictions committed before each measurement, copied unchanged. | `gibbs-recall.dat`, `t0-idle.dat`, `solve-assign.dat` | the sampling job, the idle loop, a small annealing job | | `t0-recall-4core.dat`, `gibbs-recall-4core.dat` | the same jobs on four cores at once | -Summarise any of them with `wai-quantum-fpga analyze ` or `python3 scripts/power_tables.py `. +Summarise any of them with `wai-quantum-landscape-fpga analyze ` or `python3 scripts/power_tables.py `. diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/README.md b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/README.md index 5001d33b3..b21b7b719 100644 --- a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/README.md +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/README.md @@ -9,10 +9,12 @@ are the predictions and pre-registrations, each committed before the measurement | `first-attempt/` | 08:01 UTC on the EMIO build: the run/halt step and the known-answer vehicle; then the reload refused (the session check matched its own parent process, since fixed) | | `emio-classes/` | 12:39 to 14:55 UTC on the EMIO build (sha256 `feea5015...`): step, null, settle, sampling, idle, the settle job at 50 MHz | | `emio-checks/` | the same build afterwards: conformance, agreement, latency, throughput, the processor's port widths | -| `NULL-REPEAT.txt`, `AXI-PREDICTION.txt` | written after the EMIO null failed, before the AXI build was metered | +| `AXI-PREDICTION.txt` | committed at 13:06 UTC, during the class campaign and before its null had finished, and before the AXI build was metered | +| `NULL-REPEAT.txt` | committed at 15:03 UTC, after the class campaign ended and its null had failed, before the AXI build was metered | | `axi-first/` | the first AXI build (sha256 `3d84dbd3...`, results read back by register reads): checks, then `power/` (the settle check and the repeated null) | | `JOINT-PREREG.txt` | the joint-statistics test, registered before its first board run (`axi-first/joint.txt`) | | `AXI-FINAL-PREDICTION.txt`, `axi-final/` | the final AXI build (sha256 `bb825055...`, results written back by DMA): checks, then `power/` (the settle check) | +| `build.txt` | the builds: tool and block versions, sizes, timing, each bitstream's sha256, and the testbenches' results | Summarise a meter log with `wai-quantum-landscape-fpga analyze ` or `python3 scripts/power_tables.py `. diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/build.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/build.txt new file mode 100644 index 000000000..9e63f3a1f --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/build.txt @@ -0,0 +1,33 @@ +The builds measured on 2026-11-06, and the testbenches' results. Copied from the tool's reports and +logs (its utilisation and timing reports, its build log, sha256sum), the lines that bear on the +README, without the reports' headers. + +TOOL + Vivado v2026.1 (lin64), build 6511674. Blocks: zynq_ultra_ps_e 3.5; in the AXI builds also + axi_dwidth_converter 2.1 (twice) and axi_protocol_converter 2.1. + The AXI builds ran with JOBS 1, general.maxThreads 2, under a 10 GB memory cap; the tool's own + peak, as its log reports it, was 5,140 MB. An earlier attempt with JOBS 2 and no cap, on a 15 GB + machine shared with other builds, ran the machine out of memory. + +BUILDS (part xck26-sfvc784-2LV-c, 100 MHz) + build sha256 of the .bit.bin LUTs registers BRAM tiles URAM DSP WNS WHS + AXI, final (write-back) bb82505526e33403073924373a64d3519394344e9bf6d0193634c157e42b6dfb 28145 (24.03%) 13773 4 4 20 0.993 ns 0.012 ns + AXI, first (no write-back) 3d84dbd355c9552c7593d3d5aa823284a4941acd25e5f3b7c92d5c0bc36ee441 27666 (23.62%) 13220 4 4 20 0.698 ns 0.012 ns + EMIO, one engine feea5015b2f8d85049fd48bcc66493a411d551326365f67b1db2ae65ec00074c 25539 (21.81%) 11121 0 4 20 0.294 ns 0.069 ns + EMIO, four engines 2a73f02b5a6097bd251962b7d91f5ca368180db162941465aa32129130b06b09 80927 (69.10%) 44355 0 16 80 0.441 ns 0.010 ns + (four engines: 13,925 of 14,640 slices, 95.12%; not loaded on the board this day) + All four: 0 failing endpoints, setup and hold. The AXI builds report one critical warning, a + false-path constraint for the EMIO port's synchroniser that finds no cell in them. + +TESTBENCHES, on the scripts gen-tb records from this commit's model (sha256 of each script) + EMIO landscape_tb.script bb7278bda25d95515a87e3dea3b4ee914d7de7848f9f1d5f5d1619617f3427f1 + Icarus Verilog 13.0: TB DONE: 40785 lines, 856 checked reads, 0 mismatches, sim time 6087448000 + xsim v2026.1: TB DONE: 40785 lines, 856 checked reads, 0 mismatches, sim time 6087448000 + AXI landscape_axi_tb.script 169fb7ed417a3f7feb7182668d7b6fdc4515fac8156f939c1158ed25916d6fa2 + 76 write lists, 22 batches, 22 write-backs + Icarus Verilog 13.0: TB DONE: 41127 lines, 1178 checked reads, 0 mismatches, sim time 1495786000 + xsim v2026.1: TB DONE: 41127 lines, 1178 checked reads, 0 mismatches, sim time 1495786000 + A flipped expected word in a write-back's results (xsim, the earlier script): + MISMATCH line 1231: written back chain 0 word 2 got 00000000 want f0000000 + The RTL the scripts ran against is the final AXI build's: no file under rtl/ that it is built + from has changed since that build but for comments. diff --git a/wai/wai-quantum-landscape-fpga/rtl/landscape.xdc b/wai/wai-quantum-landscape-fpga/rtl/landscape.xdc index 2fb5c0be6..278a48e52 100644 --- a/wai/wai-quantum-landscape-fpga/rtl/landscape.xdc +++ b/wai/wai-quantum-landscape-fpga/rtl/landscape.xdc @@ -1,5 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 -# The EMIO lines cross from the PS's bus clock into pl_clk0. req is synchronised in edge_emio.v; addr, +# The EMIO lines cross from the PS's bus clock into pl_clk0. req is synchronised in landscape_port.v; addr, # we and wdata are stable before req is seen; rdata is stable before ack toggles. set_false_path -to [get_cells -hierarchical -filter {NAME =~ *req_s1_reg*}] set_false_path -through [get_pins -hierarchical -filter {NAME =~ *PS8_i/EMIOGPIOO*}] diff --git a/wai/wai-quantum-landscape-fpga/scripts/build_bitstream.sh b/wai/wai-quantum-landscape-fpga/scripts/build_bitstream.sh index 9379378c6..bfedcb505 100755 --- a/wai/wai-quantum-landscape-fpga/scripts/build_bitstream.sh +++ b/wai/wai-quantum-landscape-fpga/scripts/build_bitstream.sh @@ -1,11 +1,14 @@ #!/bin/sh # SPDX-License-Identifier: Apache-2.0 # Build the engine's bitstream and convert it for the Linux FPGA manager. Needs the vendor's tool on -# PATH, at the version this design was built and measured with: Vivado 2026.1. Its no-charge licence -# tier covers this part; the design uses no licensed IP (the processing system's configuration block -# only). Run from an empty directory: +# PATH, at the version this design was built and measured with: Vivado 2026.1 (landscape.tcl stops on +# any other). Its no-charge licence tier covers this part. The design uses no licensed IP: its vendor +# blocks are the processing system's configuration and, in the AXI build, the tool's AXI width and +# protocol converters. Run from an empty directory: # sh /path/to/wai-quantum-landscape-fpga/scripts/build_bitstream.sh [MHz] [cores] [axi|emio] # The AXI link is the default; the multi-core build (cores above 1) has the EMIO link only. +# JOBS (default 4) is how many tool runs go at once. On a small machine, run with JOBS=1 and a memory +# cap: the tool peaked at 5.1 GB alone, and a machine with 15 GB shared with other work ran out. set -e FREQ=${1:-100} CORES=${2:-1} @@ -13,7 +16,7 @@ LINK=${3:-axi} if [ "$CORES" -gt 1 ]; then LINK=emio; fi HERE=$(cd "$(dirname "$0")/.." && pwd) vivado -version | head -1 -vivado -mode batch -nojournal -source "$HERE/landscape.tcl" -tclargs FREQ "$FREQ" TAG landscape JOBS 4 CORES "$CORES" LINK "$LINK" > build.log 2>&1 +vivado -mode batch -nojournal -source "$HERE/landscape.tcl" -tclargs FREQ "$FREQ" TAG landscape JOBS "${JOBS:-4}" CORES "$CORES" LINK "$LINK" > build.log 2>&1 grep -E "IMPL_STATUS|BITSTREAM" build.log BIT=$(ls ev_landscape/ev_landscape.runs/impl_1/*.bit) printf 'all:\n{\n %s\n}\n' "$BIT" > landscape.bif diff --git a/wai/wai-quantum-landscape-fpga/scripts/power_campaign.sh b/wai/wai-quantum-landscape-fpga/scripts/power_campaign.sh index d71ad09d7..7d91d289b 100755 --- a/wai/wai-quantum-landscape-fpga/scripts/power_campaign.sh +++ b/wai/wai-quantum-landscape-fpga/scripts/power_campaign.sh @@ -13,10 +13,9 @@ cd "$(dirname "$0")" OUT=${1:-power-$(date -u +%Y%m%dT%H%M%SZ)}/power; mkdir -p "$OUT" BIT=${ENGINE_BITSTREAM:-landscape.bit.bin} log() { echo "$(date -u +%FT%TZ) $*" >> "$OUT/campaign.log"; } -foreign() { pgrep -af "${WAI_QUANTUM_LANDSCAPE_FPGA_FOREIGN:-kv_|flashv|flash8|ofpga|fpgautil|aba_|wca_}" | grep -v -e pgrep -e power_campaign -e run_all -e "${KNOWN_ANSWER:-^$}" || true; } guard() { - f=$(foreign) - if [ -n "$f" ]; then log "STOP: another session is on the board: $f"; echo STOPPED > "$OUT/DONE"; exit 3; fi + # the tool leaves out itself and what it descends from: this script, and run_all.sh if that started it + f=$(./wai-quantum-landscape-fpga foreign) || { log "STOP: another session is on the board: $f"; echo STOPPED > "$OUT/DONE"; exit 3; } } log "start; $(./wai-quantum-landscape-fpga status 2>&1 | tr '\n' ' ')" guard diff --git a/wai/wai-quantum-landscape-fpga/scripts/run_all.sh b/wai/wai-quantum-landscape-fpga/scripts/run_all.sh index fc570e81c..5c77aa54b 100755 --- a/wai/wai-quantum-landscape-fpga/scripts/run_all.sh +++ b/wai/wai-quantum-landscape-fpga/scripts/run_all.sh @@ -6,26 +6,40 @@ # directory holding wai-quantum-landscape-fpga, landscape.bit.bin, this script and power_campaign.sh: # sudo setsid nohup sh run_all.sh > /dev/null 2>&1 & # and leave the board alone until results-*/power/DONE appears (about three hours). It refuses to -# start, and stops between steps, if another session is flashing or measuring. +# start, and stops between steps, if another session is flashing or measuring, and it stops if a +# check fails: a power figure on a design that failed its checks is not worth the three hours. On a +# shared board, name the other sessions' tools in WAI_QUANTUM_LANDSCAPE_FPGA_FOREIGN. cd "$(dirname "$0")" OUT=results-$(date -u +%Y%m%dT%H%M%SZ) mkdir -p "$OUT" -FOREIGN=$(pgrep -af "${WAI_QUANTUM_LANDSCAPE_FPGA_FOREIGN:-kv_|flashv|flash8|ofpga|fpgautil|aba_|wca_}" | grep -v -e pgrep -e run_all -e power_campaign || true) -[ -z "$FOREIGN" ] || { echo "another session is on the board: $FOREIGN" > "$OUT/STOPPED"; exit 3; } +B=./wai-quantum-landscape-fpga +BIT=${ENGINE_BITSTREAM:-landscape.bit.bin} +log() { echo "$(date -u +%FT%TZ) $*" >> "$OUT/log.txt"; } +FOREIGN=$($B foreign) || { echo "another session is on the board: $FOREIGN" > "$OUT/STOPPED"; exit 3; } { echo "fpga manager flags: $(cat /sys/class/fpga_manager/fpga0/flags)" echo "last FPGA write: $(dmesg | grep 'writing.*FPGA' | tail -1)" + sha256sum "$BIT" "$B" } > "$OUT/board_before.txt" # A non-zero flags word makes the manager treat a plain bitstream as something else; it is put back. FLAGS=$(cat /sys/class/fpga_manager/fpga0/flags) echo 0 > /sys/class/fpga_manager/fpga0/flags -./wai-quantum-landscape-fpga load "${ENGINE_BITSTREAM:-landscape.bit.bin}" > "$OUT/load.txt" 2>&1 || { echo "$FLAGS" > /sys/class/fpga_manager/fpga0/flags; exit 2; } -./wai-quantum-landscape-fpga status > "$OUT/status.txt" 2>&1 -./wai-quantum-landscape-fpga conform > "$OUT/conform.txt" 2>&1 -./wai-quantum-landscape-fpga agree --seeds 200 > "$OUT/agree.txt" 2>&1 -./wai-quantum-landscape-fpga joint > "$OUT/joint.txt" 2>&1 -./wai-quantum-landscape-fpga latency > "$OUT/latency.txt" 2>&1 -./wai-quantum-landscape-fpga throughput > "$OUT/throughput.txt" 2>&1 +# The flock is not held here across the steps: each step takes it itself, and would wait on this +# script for ever. Between steps the board is free, which is why the session check runs at each. +$B load "$BIT" > "$OUT/load.txt" 2>&1 || { log "load failed"; echo "$FLAGS" > /sys/class/fpga_manager/fpga0/flags; exit 2; } +$B status > "$OUT/status.txt" 2>&1 +step() { + name=$1; shift + f=$($B foreign) || { log "STOP: another session is on the board: $f"; echo "$FLAGS" > /sys/class/fpga_manager/fpga0/flags; exit 3; } + $B "$@" > "$OUT/$name.txt" 2>&1; rc=$? + log "$name exit=$rc" + [ "$rc" -eq 0 ] || { echo "$FLAGS" > /sys/class/fpga_manager/fpga0/flags; exit 4; } +} +step conform conform +step agree agree --seeds 200 +step joint joint --seed 2027 +step latency latency +step throughput throughput sh power_campaign.sh "$OUT" -./wai-quantum-landscape-fpga release >> "$OUT/status.txt" 2>&1 +$B release >> "$OUT/status.txt" 2>&1 echo "$FLAGS" > /sys/class/fpga_manager/fpga0/flags diff --git a/wai/wai-quantum-landscape-fpga/scripts/run_tb.sh b/wai/wai-quantum-landscape-fpga/scripts/run_tb.sh new file mode 100755 index 000000000..3aa4ec8cf --- /dev/null +++ b/wai/wai-quantum-landscape-fpga/scripts/run_tb.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# SPDX-License-Identifier: Apache-2.0 +# Replays the model's session against the logic in an open-source simulator (Icarus Verilog), the +# EMIO testbench and the AXI testbench, and fails on any mismatch. Needs iverilog and a Rust +# toolchain. The vendor's simulator takes the same files: the testbenches open landscape_tb.script +# and landscape_axi_tb.script in the directory they run in, so write those there with gen-tb and run +# xvlog rtl/landscape_core.v rtl/landscape_axi.v tb/tb_landscape_axi.v && xelab tb -s tbs && xsim tbs -R +# sh scripts/run_tb.sh [emio|axi|both] +set -e +WHICH=${1:-both} +HERE=$(cd "$(dirname "$0")/.." && pwd) +OUT=$(mktemp -d) +trap 'rm -rf "$OUT"' EXIT +cd "$HERE" +cargo build --locked --release +BIN=${CARGO_TARGET_DIR:-target}/release/wai-quantum-landscape-fpga +replay() { # name script gen-flag sources... + name=$1; script=$2; flag=$3; shift 3 + "$BIN" gen-tb "$OUT/$script" $flag + iverilog -g2012 -o "$OUT/$name.vvp" "$@" + (cd "$OUT" && vvp "$name.vvp") | tee "$OUT/$name.log" | tail -3 + grep -qE '^TB DONE: [0-9]+ lines, [0-9]+ checked reads, 0 mismatches' "$OUT/$name.log" || { echo "$name: the replay did not finish clean" >&2; exit 1; } +} +case "$WHICH" in emio|both) replay emio landscape_tb.script "" rtl/landscape_core.v rtl/landscape_port.v tb/tb_landscape.v ;; esac +case "$WHICH" in axi|both) replay axi landscape_axi_tb.script --axi rtl/landscape_core.v rtl/landscape_axi.v tb/tb_landscape_axi.v ;; esac diff --git a/wai/wai-quantum-landscape-fpga/scripts/step_settle.py b/wai/wai-quantum-landscape-fpga/scripts/step_settle.py index 4b9ab579d..150eb1782 100644 --- a/wai/wai-quantum-landscape-fpga/scripts/step_settle.py +++ b/wai/wai-quantum-landscape-fpga/scripts/step_settle.py @@ -1,14 +1,19 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 -"""Settle time of the run/halt switch, from `vqpu-edge meter 1 0 300` (no settle, 300 samples). +"""Settle time of the run/halt switch, from `wai-quantum-landscape-fpga meter 1 0 300` (no +settle, 300 samples). -LAST deviation, not first touch: the latest sample that sits outside a band around the segment's -final level, the band being max(2 sensor quanta = 20 mW, 3 x the tail's SD). The settle used by the -campaign must exceed it, or every point reads the previous state. +LAST deviation, not first touch, and of the level, not of one sample: the sensor throws single-sample +spikes all through a segment, so each 15-sample median is held to a band around the segment's final +level, the band being 2 sensor quanta (20 mW). The settle used by the campaign must exceed the last +window outside it, or every point reads the previous state. The last single sample outside +max(20 mW, 3 x the tail's SD) is printed beside it, for the spikes. python3 -I step_settle.py step.dat """ import statistics, sys +WINDOW = 15 + seg = {} for line in open(sys.argv[1]): if line.startswith('#') or not line.strip(): @@ -22,5 +27,8 @@ for (p, state), xs in sorted(seg.items()): final = statistics.median(tail) band = max(20.0, 3 * statistics.pstdev(tail)) last = max((i for i, x in enumerate(xs) if abs(x - final) > band), default=-1) - print(f"pass {p} {state:4s}: final {final:.1f} mW, band +-{band:.1f}, last deviation at {last + 1} s " - f"of {len(xs)}; first 10 s mean {statistics.mean(xs[:10]):.1f} mW") + medians = [statistics.median(xs[i:i + WINDOW]) for i in range(0, len(xs) - WINDOW + 1, WINDOW)] + off = max((k for k, m in enumerate(medians) if abs(m - final) > 20.0), default=-1) + print(f"pass {p} {state:4s}: final {final:.1f} mW; level settled by {(off + 1) * WINDOW} s of {len(xs)} " + f"({WINDOW}-sample medians within 20 mW after that); last single sample outside +-{band:.1f} " + f"at {last + 1} s; first 10 s mean {statistics.mean(xs[:10]):.1f} mW") diff --git a/wai/wai-quantum-landscape-fpga/src/axi.rs b/wai/wai-quantum-landscape-fpga/src/axi.rs index e08bbd80e..021d44ff7 100644 --- a/wai/wai-quantum-landscape-fpga/src/axi.rs +++ b/wai/wai-quantum-landscape-fpga/src/axi.rs @@ -10,6 +10,10 @@ //! engine is doing (an engine-map access while the DMA or the sequencer holds the engine is //! dropped and counted, never queued), and this port never makes one: it checks DROPS after every //! batch. +//! +//! The port is written against two small traits, [`Regs`] and [`Pages`], so that everything but the +//! `/dev/mem` mapping and the page table runs off the board: the tests here drive it against a +//! software link. /// The wrapper's registers and RAMs, as word addresses on the link. pub const WRAP_ID: u16 = 0x8000; @@ -52,21 +56,360 @@ pub const ST_WB_ERR: u32 = 16; /// Bytes a chain takes in the result RAM and in a write-back: eight words. pub const WB_CHAIN_BYTES: usize = 32; -#[cfg(unix)] -pub use board_port::*; +use crate::port::{ChainWords, Error, Port, ResultWords, batch_plain, bulk_plain, err}; +use crate::regs::STATUS; +use std::time::{Duration, Instant}; -#[cfg(unix)] -mod board_port { +/// A word every result word is set to before a write-back. All ones is the illegal cell pair, so a +/// result the link did not write cannot be taken for one it did. +pub const POISON: u32 = 0xFFFF_FFFF; + +const DATA_3_RO: usize = 0x06C; + +/// A window of 32-bit registers. +pub trait Regs { + fn rd(&self, off: usize) -> u32; + fn wr(&self, off: usize, v: u32); +} + +/// Pages the link reads lists from and writes results to, by physical address. +pub trait Pages { + /// Entries a page holds. + fn per_page(&self) -> usize; + /// The pages for lists; the rest are for write-backs. + fn list_pages(&self) -> usize; + /// Chains a page's write-back holds. + fn chains_per_page(&self) -> usize; + /// The page for write-back number `j` of a batch. + fn result_slot(&self, j: usize) -> usize; + /// The page's physical address, read afresh each time it is used. + fn phys(&self, k: usize) -> Result; + /// A list into page `k`, in DDR when this returns. + fn fill(&self, k: usize, list: &[(u16, u32)]); + /// `chains` results of page `k` set to [`POISON`] and pushed to DDR, nothing left in the caches. + fn poison(&self, k: usize, chains: usize); + /// Whatever the caches hold of page `k` dropped, so the next read is of DDR. + fn forget(&self, k: usize); + /// The six words of `chain` in page `k`. + fn result(&self, k: usize, chain: usize) -> ResultWords; +} + +/// Whether the processor's ports are as the AXI build is made for. See [`port_text`]. +pub fn ports_match(fpd: u32, rd: u32, wr: u32) -> bool { + (fpd >> 8) & 3 == 2 && rd & 3 == 0 && wr & 3 == 0 +} + +/// The processing system's settings for the two ports the link uses, in words: the +/// full-power-domain master's data width (FPD_SLCR AFI_FS, bits 9:8; 2 is 128 bits) and the +/// high-performance port's read and write widths (AFIFM2 RDCTRL and WRCTRL, bits 1:0; 0 is 128 +/// bits). The DMA reads through that port and the write-back writes through it, so both widths count. +pub fn port_text(fpd: u32, rd: u32, wr: u32) -> String { + let w = |v: u32, map: [&'static str; 4]| map[(v & 3) as usize]; + format!( + "FPD_SLCR AFI_FS {fpd:#010x}: M_AXI_HPM0_FPD {} bits; AFIFM2 RDCTRL {rd:#010x} WRCTRL {wr:#010x}: S_AXI_HP0_FPD reads {} bits, writes {} bits (the AXI build is made for 128 on all three)", + w(fpd >> 8, ["32", "64", "128", "?"]), + w(rd, ["128", "64", "32", "?"]), + w(wr, ["128", "64", "32", "?"]) + ) +} + +pub struct AxiPort { + lite: R, + gpio: R, + /// Check the signature before every access (on unless a measurement turns it off). + pub guard: bool, + pub dma: Option, + /// Lists shorter than this go as plain writes. + pub dma_min: usize, + /// Batches go to the sequencer; off, they go as plain accesses (for comparison). + pub sequencer: bool, + pub accesses: u64, + pub dma_lists: u64, + pub batches: u64, + pub write_backs: u64, +} + +impl AxiPort { + /// The link's ID and version over AXI, behind the signature. + pub fn attach(lite: R, gpio: R, dma: Option) -> Result { + let sig = gpio.rd(DATA_3_RO); + if sig & 0xFF != SIGNATURE { + return err(format!( + "the programmable logic does not carry the AXI link's signature (EMIO bank 3 reads {sig:#010x}, want {SIGNATURE:#x} in its low byte), \ + so no AXI address is touched. Load the AXI build first; if it was loaded, another session has replaced it." + )); + } + let mut p = AxiPort { lite, gpio, guard: true, dma: None, dma_min: 32, sequencer: true, accesses: 0, dma_lists: 0, batches: 0, write_backs: 0 }; + let id = p.rd(WRAP_ID)?; + if id != WRAP_ID_VALUE { + return err(format!("the AXI link answers {id:#010x} for its ID, want {WRAP_ID_VALUE:#010x}")); + } + let v = p.rd(WRAP_VERSION)?; + if v != WRAP_VERSION_VALUE { + return err(format!("AXI link version {v}, this host speaks {WRAP_VERSION_VALUE}")); + } + let st = p.rd(WRAP_STATUS)?; + if st & (ST_SEQ | ST_DMA | ST_WB) != 0 { + return err(format!("the AXI link is busy at open (status {st:#x}): another process is using it")); + } + p.wr(WRAP_DROPS, 0)?; + p.dma = dma; + Ok(p) + } + + pub fn name(&self) -> &'static str { + "AXI" + } + + #[inline] + pub fn signature(&self) -> u32 { + self.gpio.rd(DATA_3_RO) + } + + #[inline] + fn check(&self) -> Result<(), Error> { + if self.guard && self.gpio.rd(DATA_3_RO) & 0xFF != SIGNATURE { + return err("the AXI link's signature is gone: another session has replaced the design. Stopped before the next AXI access."); + } + Ok(()) + } + + /// A read of any word on the link, guarded. + #[inline] + pub fn rd(&mut self, word: u16) -> Result { + self.check()?; + self.accesses += 1; + Ok(self.lite.rd(word as usize * 4)) + } + + /// A write of any word on the link, guarded. + #[inline] + pub fn wr(&mut self, word: u16, v: u32) -> Result<(), Error> { + self.check()?; + self.accesses += 1; + self.lite.wr(word as usize * 4, v); + Ok(()) + } + + /// Unguarded, for the latency measurement only. + #[inline] + pub fn rd_raw(&self, word: u16) -> u32 { + self.lite.rd(word as usize * 4) + } + #[inline] + pub fn wr_raw(&self, word: u16, v: u32) { + self.lite.wr(word as usize * 4, v) + } + + fn wait_wrap(&mut self, bits: u32, timeout: Duration, what: &str) -> Result { + let t0 = Instant::now(); + let mut k = 0u32; + loop { + let st = self.rd(WRAP_STATUS)?; + if st & bits == 0 { + return Ok(st); + } + k += 1; + if k % 256 == 0 && t0.elapsed() > timeout { + return err(format!("{what} still busy after {timeout:?} (status {st:#x})")); + } + } + } + + fn pages(&self) -> Result<&D, Error> { + self.dma.as_ref().ok_or_else(|| Error("no DMA pages".into())) + } + + /// A page that moved while the link was using it held nothing the link saw. The kernel may move + /// a locked page; this catches it after the fact (README, what is not done). + fn still_at(&self, slot: usize, pa: u64) -> Result<(), Error> { + let now = self.pages()?.phys(slot)?; + if now != pa { + return err(format!("a DMA page moved from {pa:#x} to {now:#x} during a transfer: what it carried is not kept")); + } + Ok(()) + } + + /// One list of at most a page, by DMA, waited for. + fn dma_page(&mut self, slot: usize, list: &[(u16, u32)], timeout: Duration) -> Result<(), Error> { + let pa = { + let d = self.pages()?; + d.fill(slot, list); + d.phys(slot)? + }; + let before = self.rd(DMA_DONE)?; + self.wr(DMA_LO, pa as u32)?; + self.wr(DMA_HI, (pa >> 32) as u32)?; + self.wr(DMA_COUNT, list.len() as u32)?; + self.wr(DMA_GO, 1)?; + let st = self.wait_wrap(ST_DMA, timeout, "the DMA")?; + if st & ST_DMA_ERR != 0 { + return err(format!("the DMA's read of the list at {pa:#x} came back with an error")); + } + let after = self.rd(DMA_DONE)?; + if after != before.wrapping_add(1) { + return err(format!("the DMA did not run (done count {before} -> {after})")); + } + self.still_at(slot, pa)?; + self.dma_lists += 1; + Ok(()) + } + + /// A write list of any length, a page at a time. + pub fn dma_list(&mut self, list: &[(u16, u32)], timeout: Duration) -> Result<(), Error> { + let (per, pages) = match &self.dma { + Some(d) => (d.per_page(), d.list_pages()), + None => return bulk_plain(self, list), + }; + for (k, chunk) in list.chunks(per).enumerate() { + self.dma_page(k % pages, chunk, timeout)?; + } + Ok(()) + } + + /// The first `chains` results of the result RAM, written back to DDR a page at a time. + pub fn write_back(&mut self, chains: usize, timeout: Duration) -> Result, Error> { + let per = self.pages()?.chains_per_page(); + let mut out = Vec::with_capacity(chains); + for (j, first) in (0..chains).step_by(per).enumerate() { + let n = per.min(chains - first); + let (slot, pa) = { + let d = self.pages()?; + let slot = d.result_slot(j); + d.poison(slot, n); + (slot, d.phys(slot)?) + }; + let before = self.rd(WB_DONE)?; + self.wr(WB_LO, pa as u32)?; + self.wr(WB_HI, (pa >> 32) as u32)?; + self.wr(WB_FIRST, first as u32)?; + self.wr(WB_CHAINS, n as u32)?; + self.wr(WB_GO, 1)?; + let st = self.wait_wrap(ST_WB, timeout, "the write-back")?; + if st & ST_WB_ERR != 0 { + return err(format!("the write-back to {pa:#x} came back with an error")); + } + let after = self.rd(WB_DONE)?; + if after != before.wrapping_add(1) { + return err(format!("the write-back did not run (done count {before} -> {after})")); + } + self.still_at(slot, pa)?; + let d = self.pages()?; + d.forget(slot); + for k in 0..n { + let r = d.result(slot, k); + if r[..4].contains(&POISON) { + return err(format!("the write-back reported done, and chain {} of it still holds what was there before it", first + k)); + } + out.push(r); + } + self.write_backs += 1; + } + Ok(out) + } + + fn drops_zero(&mut self) -> Result<(), Error> { + let d = self.rd(WRAP_DROPS)?; + if d != 0 { + return err(format!("{d} engine accesses were dropped while the link held the engine")); + } + Ok(()) + } +} + +impl Port for AxiPort { + fn write(&mut self, addr: u16, val: u32) -> Result<(), Error> { + if addr >= 0x8000 { + return err(format!("{addr:#06x} is not an engine register")); + } + self.wr(addr, val) + } + fn read(&mut self, addr: u16) -> Result { + if addr >= 0x8000 { + return err(format!("{addr:#06x} is not an engine register")); + } + self.rd(addr) + } + fn wait_idle(&mut self, timeout: Duration) -> Result<(), Error> { + let t0 = Instant::now(); + let mut k = 0u32; + loop { + let st = self.rd(STATUS)?; + if st & 1 == 0 { + return Ok(()); + } + k += 1; + if k % 256 == 0 && t0.elapsed() > timeout { + return err(format!("the engine still busy after {timeout:?} (status {st:#x})")); + } + } + } + fn bulk(&mut self, list: &[(u16, u32)]) -> Result<(), Error> { + if list.len() < self.dma_min || self.dma.is_none() { + return bulk_plain(self, list); + } + self.dma_list(list, Duration::from_secs(5)) + } + fn batch(&mut self, chains: &[ChainWords], timeout: Duration) -> Result, Error> { + if !self.sequencer { + return batch_plain(self, chains, timeout); + } + let mut out = Vec::with_capacity(chains.len()); + for group in chains.chunks(SEQ_MAX) { + let mut list = Vec::with_capacity(group.len() * 6); + for (k, c) in group.iter().enumerate() { + for (i, &w) in c.iter().enumerate() { + list.push((CHAIN_RAM + (8 * k + i) as u16, w)); + } + } + if self.dma.is_some() { + self.dma_list(&list, timeout)?; + } else { + for &(a, v) in &list { + self.wr(a, v)?; + } + } + let before = self.rd(SEQ_DONE)?; + self.wr(SEQ_COUNT, group.len() as u32)?; + self.wr(SEQ_GO, 1)?; + if let Err(e) = self.wait_wrap(ST_SEQ, timeout, "the sequencer") { + let _ = self.wr(SEQ_ABORT, 1); + return Err(e); + } + let after = self.rd(SEQ_DONE)?; + if after.wrapping_sub(before) & 0xFFFF != group.len() as u32 { + return err(format!("the sequencer ran {} chains of {}", after.wrapping_sub(before) & 0xFFFF, group.len())); + } + if self.dma.is_some() { + out.extend(self.write_back(group.len(), timeout)?); + } else { + for k in 0..group.len() { + let mut r = [0u32; 6]; + for (i, w) in r.iter_mut().enumerate() { + *w = self.rd(RESULT_RAM + (8 * k + i) as u16)?; + } + out.push(r); + } + } + self.drops_zero()?; + self.batches += 1; + } + Ok(out) + } +} + +#[cfg(target_os = "linux")] +pub use board_side::*; + +/// What only the board has: `/dev/mem` and the page table. +#[cfg(target_os = "linux")] +mod board_side { use super::*; use crate::emio::Mem; - use crate::port::{ChainWords, Error, Port, ResultWords, batch_plain, bulk_plain, err}; - use crate::regs::STATUS; use std::fs::File; use std::os::unix::fs::FileExt; - use std::time::{Duration, Instant}; const GPIO_BASE: u64 = 0xFF0A_0000; - const DATA_3_RO: usize = 0x06C; unsafe extern "C" { fn mmap(addr: *mut u8, len: usize, prot: i32, flags: i32, fd: i32, off: i64) -> *mut u8; @@ -80,6 +423,17 @@ mod board_port { const MAP_POPULATE: i32 = 0x8000; const SC_PAGESIZE: i32 = 30; + impl Regs for Mem { + #[inline] + fn rd(&self, off: usize) -> u32 { + Mem::rd(self, off) + } + #[inline] + fn wr(&self, off: usize, v: u32) { + Mem::wr(self, off, v) + } + } + /// The data cache's line size, from CTR_EL0. #[cfg(target_arch = "aarch64")] fn dline() -> usize { @@ -121,7 +475,9 @@ mod board_port { #[cfg(not(target_arch = "aarch64"))] fn flush(_p: *const u8, _len: usize) {} - /// Pinned pages for write lists, and their physical addresses from /proc/self/pagemap (root). + /// Locked pages for write lists and results, and their physical addresses from + /// /proc/self/pagemap (root). Locked is not pinned: the kernel may still move such a page, and + /// a kernel-owned buffer is what would rule that out (README, what is not done). pub struct Dma { buf: *mut u8, pages: usize, @@ -138,7 +494,7 @@ mod board_port { let len = pages * page; let buf = unsafe { mmap(std::ptr::null_mut(), len, PROT_RW, MAP_PRIVATE | MAP_ANONYMOUS | MAP_LOCKED | MAP_POPULATE, -1, 0) }; if buf as isize == -1 || buf.is_null() { - return err("could not pin pages for the DMA's lists"); + return err("could not lock pages for the DMA's lists"); } let pagemap = File::open("/proc/self/pagemap").map_err(|e| Error(format!("/proc/self/pagemap: {e}")))?; let d = Dma { buf, pages, page, pagemap }; @@ -147,14 +503,22 @@ mod board_port { } Ok(d) } + } - /// Entries a page holds. - pub fn per_page(&self) -> usize { + impl Pages for Dma { + fn per_page(&self) -> usize { self.page / 8 } - - /// The physical address of page `k`, read afresh each time it is used. - pub fn phys(&self, k: usize) -> Result { + fn list_pages(&self) -> usize { + self.pages - 2 + } + fn chains_per_page(&self) -> usize { + self.page / WB_CHAIN_BYTES + } + fn result_slot(&self, j: usize) -> usize { + self.pages - 1 - j % 2 + } + fn phys(&self, k: usize) -> Result { let va = self.buf as usize + k * self.page; let mut e = [0u8; 8]; self.pagemap.read_exact_at(&mut e, (va / self.page * 8) as u64).map_err(|x| Error(format!("pagemap: {x}")))?; @@ -165,25 +529,23 @@ mod board_port { } Ok(pfn * self.page as u64) } - - /// Chains a page's write-back holds. - pub fn chains_per_page(&self) -> usize { - self.page / WB_CHAIN_BYTES + fn fill(&self, k: usize, list: &[(u16, u32)]) { + let p = unsafe { self.buf.add(k * self.page) } as *mut u64; + for (i, &(a, v)) in list.iter().enumerate() { + unsafe { std::ptr::write_volatile(p.add(i), ((a as u64) << 32) | v as u64) }; + } + clean(p as *const u8, list.len() * 8); } - - /// The pages for lists; the last two are for write-backs. - fn list_pages(&self) -> usize { - self.pages - 2 + fn poison(&self, k: usize, chains: usize) { + let p = unsafe { self.buf.add(k * self.page) } as *mut u32; + for i in 0..chains * WB_CHAIN_BYTES / 4 { + unsafe { std::ptr::write_volatile(p.add(i), POISON) }; + } + flush(p as *const u8, self.page); } - - fn result_slot(&self, j: usize) -> usize { - self.pages - 1 - j % 2 - } - - fn flush_page(&self, k: usize) { + fn forget(&self, k: usize) { flush(unsafe { self.buf.add(k * self.page) }, self.page); } - fn result(&self, k: usize, chain: usize) -> ResultWords { let p = unsafe { self.buf.add(k * self.page + chain * WB_CHAIN_BYTES) } as *const u32; let mut r = [0u32; 6]; @@ -192,14 +554,6 @@ mod board_port { } r } - - fn fill(&self, k: usize, list: &[(u16, u32)]) { - let p = unsafe { self.buf.add(k * self.page) } as *mut u64; - for (i, &(a, v)) in list.iter().enumerate() { - unsafe { std::ptr::write_volatile(p.add(i), ((a as u64) << 32) | v as u64) }; - } - clean(p as *const u8, list.len() * 8); - } } impl Drop for Dma { @@ -210,297 +564,347 @@ mod board_port { } } + pub type BoardAxi = AxiPort; + /// Whether the loaded design carries the AXI link's signature: a GPIO read, safe whatever is loaded. pub fn signature_present() -> bool { Mem::map(GPIO_BASE, 0x1000).map(|g| g.rd(DATA_3_RO) & 0xFF == SIGNATURE).unwrap_or(false) } - /// The processing system's settings for the two ports the link uses, as the board's boot left - /// them (read only; loading a bitstream through the FPGA manager does not change them): the - /// full-power-domain master's data width (FPD_SLCR AFI_FS, bits 9:8; 2 is 128 bits) and the - /// high-performance port's read and write widths (AFIFM2 RDCTRL and WRCTRL, bits 1:0; 0 is 128 - /// bits). The AXI build is made for 128 bits on both, and a different width would put data on - /// the wrong byte lanes. Returns whether they match, and the sentence that says so. + /// The two ports' widths as the board's boot left them (read only; loading a bitstream through + /// the FPGA manager does not change them): whether they match, and the sentence that says so. pub fn ps_ports() -> Result<(bool, String), Error> { let fpd = Mem::map(0xFD61_5000, 0x1000)?.rd(0); let afi = Mem::map(0xFD38_0000, 0x1000)?; let (rd, wr) = (afi.rd(0x000), afi.rd(0x014)); - let w = |v: u32, map: [&'static str; 4]| map[(v & 3) as usize]; - let text = format!( - "FPD_SLCR AFI_FS {fpd:#010x}: M_AXI_HPM0_FPD {} bits; AFIFM2 RDCTRL {rd:#010x} WRCTRL {wr:#010x}: S_AXI_HP0_FPD reads {} bits, writes {} bits (the AXI build is made for 128 on the master and for reads)", - w(fpd >> 8, ["32", "64", "128", "?"]), - w(rd, ["128", "64", "32", "?"]), - w(wr, ["128", "64", "32", "?"]) - ); - Ok(((fpd >> 8) & 3 == 2 && rd & 3 == 0, text)) + Ok((ports_match(fpd, rd, wr), port_text(fpd, rd, wr))) } - pub struct AxiPort { - lite: Mem, - gpio: Mem, - /// Check the signature before every access (on unless a measurement turns it off). - pub guard: bool, - pub dma: Option, - /// Lists shorter than this go as plain writes. - pub dma_min: usize, - /// Batches go to the sequencer; off, they go as plain accesses (for comparison). - pub sequencer: bool, - pub accesses: u64, - pub dma_lists: u64, - pub batches: u64, - pub write_backs: u64, - } - - impl AxiPort { - /// Opens the link: the signature first, then the wrapper's ID and version over AXI. - pub fn open() -> Result { - let gpio = Mem::map(GPIO_BASE, 0x1000)?; - let sig = gpio.rd(DATA_3_RO); - if sig & 0xFF != SIGNATURE { - return err(format!( - "the programmable logic does not carry the AXI link's signature (EMIO bank 3 reads {sig:#010x}, want {SIGNATURE:#x} in its low byte), \ - so no AXI address is touched. Load the AXI build first; if it was loaded, another session has replaced it." - )); - } - let (ok, text) = ps_ports()?; - if !ok { - return err(format!("the processing system's ports are not set as the AXI build needs, so no AXI address is touched: {text}")); - } - let lite = Mem::map(LITE_BASE, LITE_LEN)?; - let mut p = AxiPort { lite, gpio, guard: true, dma: None, dma_min: 32, sequencer: true, accesses: 0, dma_lists: 0, batches: 0, write_backs: 0 }; - let id = p.rd(WRAP_ID)?; - if id != WRAP_ID_VALUE { - return err(format!("the AXI link answers {id:#010x} for its ID, want {WRAP_ID_VALUE:#010x}")); - } - let v = p.rd(WRAP_VERSION)?; - if v != WRAP_VERSION_VALUE { - return err(format!("AXI link version {v}, this host speaks {WRAP_VERSION_VALUE}")); - } - let st = p.rd(WRAP_STATUS)?; - if st & (ST_SEQ | ST_DMA) != 0 { - return err(format!("the AXI link is busy at open (status {st:#x}): another process is using it")); - } - p.wr(WRAP_DROPS, 0)?; - p.dma = Some(Dma::new(16)?); - Ok(p) + /// Opens the board's link: the signature and the port widths first, both by registers that always + /// answer, then the wrapper's ID and version over AXI. + pub fn open() -> Result { + let gpio = Mem::map(GPIO_BASE, 0x1000)?; + let sig = gpio.rd(DATA_3_RO); + if sig & 0xFF != SIGNATURE { + return err(format!( + "the programmable logic does not carry the AXI link's signature (EMIO bank 3 reads {sig:#010x}, want {SIGNATURE:#x} in its low byte), \ + so no AXI address is touched. Load the AXI build first; if it was loaded, another session has replaced it." + )); } - - pub fn name(&self) -> &'static str { - "AXI" - } - - #[inline] - pub fn signature(&self) -> u32 { - self.gpio.rd(DATA_3_RO) - } - - #[inline] - fn check(&self) -> Result<(), Error> { - if self.guard && self.gpio.rd(DATA_3_RO) & 0xFF != SIGNATURE { - return err("the AXI link's signature is gone: another session has replaced the design. Stopped before the next AXI access."); - } - Ok(()) - } - - /// A read of any word on the link, guarded. - #[inline] - pub fn rd(&mut self, word: u16) -> Result { - self.check()?; - self.accesses += 1; - Ok(self.lite.rd(word as usize * 4)) - } - - /// A write of any word on the link, guarded. - #[inline] - pub fn wr(&mut self, word: u16, v: u32) -> Result<(), Error> { - self.check()?; - self.accesses += 1; - self.lite.wr(word as usize * 4, v); - Ok(()) - } - - /// Unguarded, for the latency measurement only. - #[inline] - pub fn rd_raw(&self, word: u16) -> u32 { - self.lite.rd(word as usize * 4) - } - #[inline] - pub fn wr_raw(&self, word: u16, v: u32) { - self.lite.wr(word as usize * 4, v) - } - - fn wait_wrap(&mut self, bits: u32, timeout: Duration, what: &str) -> Result { - let t0 = Instant::now(); - let mut k = 0u32; - loop { - let st = self.rd(WRAP_STATUS)?; - if st & bits == 0 { - return Ok(st); - } - k += 1; - if k % 256 == 0 && t0.elapsed() > timeout { - return err(format!("{what} still busy after {timeout:?} (status {st:#x})")); - } - } - } - - /// One list of at most a page, by DMA, waited for. - fn dma_page(&mut self, slot: usize, list: &[(u16, u32)], timeout: Duration) -> Result<(), Error> { - let pa = { - let d = self.dma.as_ref().ok_or_else(|| Error("no DMA pages".into()))?; - d.fill(slot, list); - d.phys(slot)? - }; - let before = self.rd(DMA_DONE)?; - self.wr(DMA_LO, pa as u32)?; - self.wr(DMA_HI, (pa >> 32) as u32)?; - self.wr(DMA_COUNT, list.len() as u32)?; - self.wr(DMA_GO, 1)?; - let st = self.wait_wrap(ST_DMA, timeout, "the DMA")?; - if st & ST_DMA_ERR != 0 { - return err(format!("the DMA's read of the list at {pa:#x} came back with an error")); - } - let after = self.rd(DMA_DONE)?; - if after != before.wrapping_add(1) { - return err(format!("the DMA did not run (done count {before} -> {after})")); - } - self.dma_lists += 1; - Ok(()) - } - - /// A write list of any length, a page at a time. - pub fn dma_list(&mut self, list: &[(u16, u32)], timeout: Duration) -> Result<(), Error> { - let (per, pages) = match &self.dma { - Some(d) => (d.per_page(), d.list_pages()), - None => return bulk_plain(self, list), - }; - for (k, chunk) in list.chunks(per).enumerate() { - self.dma_page(k % pages, chunk, timeout)?; - } - Ok(()) - } - - /// The first `chains` results of the result RAM, written back to DDR a page at a time. - pub fn write_back(&mut self, chains: usize, timeout: Duration) -> Result, Error> { - let per = self.dma.as_ref().ok_or_else(|| Error("no DMA pages".into()))?.chains_per_page(); - let mut out = Vec::with_capacity(chains); - for (j, first) in (0..chains).step_by(per).enumerate() { - let n = per.min(chains - first); - let (slot, pa) = { - let d = self.dma.as_ref().ok_or_else(|| Error("no DMA pages".into()))?; - let slot = d.result_slot(j); - d.flush_page(slot); - (slot, d.phys(slot)?) - }; - let before = self.rd(WB_DONE)?; - self.wr(WB_LO, pa as u32)?; - self.wr(WB_HI, (pa >> 32) as u32)?; - self.wr(WB_FIRST, first as u32)?; - self.wr(WB_CHAINS, n as u32)?; - self.wr(WB_GO, 1)?; - let st = self.wait_wrap(ST_WB, timeout, "the write-back")?; - if st & ST_WB_ERR != 0 { - return err(format!("the write-back to {pa:#x} came back with an error")); - } - let after = self.rd(WB_DONE)?; - if after != before.wrapping_add(1) { - return err(format!("the write-back did not run (done count {before} -> {after})")); - } - let d = self.dma.as_ref().ok_or_else(|| Error("no DMA pages".into()))?; - d.flush_page(slot); - out.extend((0..n).map(|k| d.result(slot, k))); - self.write_backs += 1; - } - Ok(out) - } - - fn drops_zero(&mut self) -> Result<(), Error> { - let d = self.rd(WRAP_DROPS)?; - if d != 0 { - return err(format!("{d} engine accesses were dropped while the link held the engine")); - } - Ok(()) - } - } - - impl Port for AxiPort { - fn write(&mut self, addr: u16, val: u32) -> Result<(), Error> { - if addr >= 0x8000 { - return err(format!("{addr:#06x} is not an engine register")); - } - self.wr(addr, val) - } - fn read(&mut self, addr: u16) -> Result { - if addr >= 0x8000 { - return err(format!("{addr:#06x} is not an engine register")); - } - self.rd(addr) - } - fn wait_idle(&mut self, timeout: Duration) -> Result<(), Error> { - let t0 = Instant::now(); - let mut k = 0u32; - loop { - let st = self.rd(STATUS)?; - if st & 1 == 0 { - return Ok(()); - } - k += 1; - if k % 256 == 0 && t0.elapsed() > timeout { - return err(format!("the engine still busy after {timeout:?} (status {st:#x})")); - } - } - } - fn bulk(&mut self, list: &[(u16, u32)]) -> Result<(), Error> { - if list.len() < self.dma_min || self.dma.is_none() { - return bulk_plain(self, list); - } - self.dma_list(list, Duration::from_secs(5)) - } - fn batch(&mut self, chains: &[ChainWords], timeout: Duration) -> Result, Error> { - if !self.sequencer { - return batch_plain(self, chains, timeout); - } - let mut out = Vec::with_capacity(chains.len()); - for group in chains.chunks(SEQ_MAX) { - let mut list = Vec::with_capacity(group.len() * 6); - for (k, c) in group.iter().enumerate() { - for (i, &w) in c.iter().enumerate() { - list.push((CHAIN_RAM + (8 * k + i) as u16, w)); - } - } - if self.dma.is_some() { - self.dma_list(&list, timeout)?; - } else { - for &(a, v) in &list { - self.wr(a, v)?; - } - } - let before = self.rd(SEQ_DONE)?; - self.wr(SEQ_COUNT, group.len() as u32)?; - self.wr(SEQ_GO, 1)?; - if let Err(e) = self.wait_wrap(ST_SEQ, timeout, "the sequencer") { - let _ = self.wr(SEQ_ABORT, 1); - return Err(e); - } - let after = self.rd(SEQ_DONE)?; - if after.wrapping_sub(before) & 0xFFFF != group.len() as u32 { - return err(format!("the sequencer ran {} chains of {}", after.wrapping_sub(before) & 0xFFFF, group.len())); - } - if self.dma.is_some() { - out.extend(self.write_back(group.len(), timeout)?); - } else { - for k in 0..group.len() { - let mut r = [0u32; 6]; - for (i, w) in r.iter_mut().enumerate() { - *w = self.rd(RESULT_RAM + (8 * k + i) as u16)?; - } - out.push(r); - } - } - self.drops_zero()?; - self.batches += 1; - } - Ok(out) + let (ok, text) = ps_ports()?; + if !ok { + return err(format!("the processing system's ports are not set as the AXI build needs, so no AXI address is touched: {text}")); } + let lite = Mem::map(LITE_BASE, LITE_LEN)?; + AxiPort::attach(lite, gpio, Some(Dma::new(16)?)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::conform::bits; + use crate::engine::Engine; + use crate::member::{BoardInfo, BoardMember}; + use crate::model::ModelPort; + use crate::port::batch_plain; + use std::cell::RefCell; + use std::rc::Rc; + + const PAGE: usize = 4096; + const BASE: u64 = 0x1000_0000; + + /// The link in software: the wrapper's registers and RAMs, over the model of the engine, with + /// its DMA and write-back working on [`FakePages`]. What a test wants broken, it sets here. + struct Link { + model: ModelPort, + pages: Rc>>>, + signature: u32, + chain_ram: Vec, + result_ram: Vec, + regs: std::collections::HashMap, + dma_done: u32, + seq_done: u32, + wb_done: u32, + drops: u32, + lite_accesses: u64, + // faults + wb_writes_nothing: bool, + seq_loses_one: bool, + wb_swaps_chains: bool, + } + + impl Link { + fn reg(&self, a: u16) -> u32 { + self.regs.get(&a).copied().unwrap_or(0) + } + fn word(&self, addr: u64) -> usize { + (addr - BASE) as usize + } + fn write(&mut self, a: u16, v: u32) { + match a { + DMA_GO => { + let at = self.word(((self.reg(DMA_HI) as u64) << 32) | self.reg(DMA_LO) as u64); + for i in 0..self.reg(DMA_COUNT) as usize { + let e = u64::from_le_bytes(self.pages.borrow()[at / PAGE][at % PAGE + 8 * i..at % PAGE + 8 * i + 8].try_into().unwrap()); + let (ea, ev) = ((e >> 32) as u16, e as u32); + if ea >= CHAIN_RAM { + self.chain_ram[(ea - CHAIN_RAM) as usize] = ev; + } else { + self.model.write(ea, ev).unwrap(); + } + } + self.dma_done += 1; + } + SEQ_GO => { + let n = self.reg(SEQ_COUNT) as usize; + let chains: Vec = (0..n).map(|k| std::array::from_fn(|i| self.chain_ram[8 * k + i])).collect(); + for (k, r) in batch_plain(&mut self.model, &chains, Duration::from_secs(1)).unwrap().iter().enumerate() { + self.result_ram[8 * k..8 * k + 6].copy_from_slice(r); + } + self.seq_done += (n - usize::from(self.seq_loses_one)) as u32; + } + WB_GO => { + let at = self.word(((self.reg(WB_HI) as u64) << 32) | self.reg(WB_LO) as u64); + if !self.wb_writes_nothing { + let first = self.reg(WB_FIRST) as usize; + for k in 0..self.reg(WB_CHAINS) as usize { + let from = if self.wb_swaps_chains { k ^ 1 } else { k }; + for i in 0..8 { + let w = self.result_ram[8 * (first + from) + i]; + let o = at % PAGE + k * WB_CHAIN_BYTES + 4 * i; + self.pages.borrow_mut()[at / PAGE][o..o + 4].copy_from_slice(&w.to_le_bytes()); + } + } + } + self.wb_done += 1; + } + WRAP_DROPS => self.drops = 0, + a if a < 0x8000 => self.model.write(a, v).unwrap(), + a if (CHAIN_RAM..CHAIN_RAM + 0x800).contains(&a) => self.chain_ram[(a - CHAIN_RAM) as usize] = v, + a => { + self.regs.insert(a, v); + } + } + } + fn read(&mut self, a: u16) -> u32 { + match a { + WRAP_ID => WRAP_ID_VALUE, + WRAP_VERSION => WRAP_VERSION_VALUE, + WRAP_STATUS => 0, + DMA_DONE => self.dma_done, + SEQ_DONE => self.seq_done, + WB_DONE => self.wb_done, + WRAP_DROPS => self.drops, + a if a < 0x8000 => self.model.read(a).unwrap(), + a if (RESULT_RAM..RESULT_RAM + 0x800).contains(&a) => self.result_ram[(a - RESULT_RAM) as usize], + a => self.reg(a), + } + } + } + + #[derive(Clone)] + struct Bus { + link: Rc>, + gpio: bool, + } + + impl Regs for Bus { + fn rd(&self, off: usize) -> u32 { + let mut l = self.link.borrow_mut(); + if self.gpio { + return l.signature; + } + l.lite_accesses += 1; + l.read((off / 4) as u16) + } + fn wr(&self, off: usize, v: u32) { + assert!(!self.gpio); + let mut l = self.link.borrow_mut(); + l.lite_accesses += 1; + l.write((off / 4) as u16, v); + } + } + + struct FakePages { + pages: Rc>>>, + /// Whether the kernel moves a page between the address's two reads. + moves: std::cell::Cell, + reads: std::cell::Cell, + } + + impl Pages for FakePages { + fn per_page(&self) -> usize { + PAGE / 8 + } + fn list_pages(&self) -> usize { + self.pages.borrow().len() - 2 + } + fn chains_per_page(&self) -> usize { + PAGE / WB_CHAIN_BYTES + } + fn result_slot(&self, j: usize) -> usize { + self.pages.borrow().len() - 1 - j % 2 + } + fn phys(&self, k: usize) -> Result { + self.reads.set(self.reads.get() + 1); + let moved = self.moves.get() && self.reads.get() % 2 == 0; + Ok(BASE + (k * PAGE) as u64 + if moved { 0x100_0000 } else { 0 }) + } + fn fill(&self, k: usize, list: &[(u16, u32)]) { + for (i, &(a, v)) in list.iter().enumerate() { + self.pages.borrow_mut()[k][8 * i..8 * i + 8].copy_from_slice(&(((a as u64) << 32) | v as u64).to_le_bytes()); + } + } + fn poison(&self, k: usize, chains: usize) { + self.pages.borrow_mut()[k][..chains * WB_CHAIN_BYTES].fill(0xFF); + } + fn forget(&self, _k: usize) {} + fn result(&self, k: usize, chain: usize) -> ResultWords { + let p = self.pages.borrow(); + std::array::from_fn(|i| { + let o = chain * WB_CHAIN_BYTES + 4 * i; + u32::from_le_bytes(p[k][o..o + 4].try_into().unwrap()) + }) + } + } + + type FakePort = AxiPort; + + fn fake() -> (FakePort, Rc>) { + let pages = Rc::new(RefCell::new(vec![vec![0u8; PAGE]; 16])); + let link = Rc::new(RefCell::new(Link { + model: ModelPort::new(), + pages: pages.clone(), + signature: SIGNATURE, + chain_ram: vec![0; 2048], + result_ram: vec![0; 2048], + regs: Default::default(), + dma_done: 0, + seq_done: 0, + wb_done: 0, + drops: 0, + lite_accesses: 0, + wb_writes_nothing: false, + seq_loses_one: false, + wb_swaps_chains: false, + })); + let p = AxiPort::attach(Bus { link: link.clone(), gpio: false }, Bus { link: link.clone(), gpio: true }, Some(FakePages { pages, moves: Default::default(), reads: Default::default() })).unwrap(); + (p, link) + } + + fn chains(n: u32) -> Vec { + (0..n).map(|k| [k & 1, 0, 0, 0, k, 0]).collect() + } + + /// A one-cell job through the port, set up by plain accesses. + fn set_up(p: &mut FakePort) { + p.write(crate::regs::MODE, crate::regs::MODE_T0).unwrap(); + p.write(crate::regs::BOUND, 3).unwrap(); + } + + #[test] + fn the_port_gives_the_model_s_bits_through_dma_sequencer_and_write_back() { + let (p, link) = fake(); + let m = BoardMember::new(Engine::open(p).unwrap(), BoardInfo::default()); + let b = bits(&m).unwrap(); + assert!(b.settle_ok && b.chains_ok, "{}", b.text); + let e = m.engine.borrow(); + assert!(e.port.dma_lists > 0 && e.port.batches > 0 && e.port.write_backs > 0, "{} {} {}", e.port.dma_lists, e.port.batches, e.port.write_backs); + assert_eq!(link.borrow().drops, 0); + } + + #[test] + fn more_chains_than_a_page_holds_come_back_in_order() { + let (mut p, _) = fake(); + set_up(&mut p); + let got = p.batch(&chains(300), Duration::from_secs(1)).unwrap(); + assert_eq!(got.len(), 300); + let (mut q, _) = fake(); + set_up(&mut q); + q.sequencer = false; + assert_eq!(got, q.batch(&chains(300), Duration::from_secs(1)).unwrap()); + } + + #[test] + fn no_access_is_made_without_the_signature() { + let (mut p, link) = fake(); + let before = link.borrow().lite_accesses; + link.borrow_mut().signature = 0; + assert!(p.rd(WRAP_ID).is_err()); + assert!(p.wr(DMA_COUNT, 1).is_err()); + assert!(p.read(crate::regs::ID).is_err()); + assert_eq!(link.borrow().lite_accesses, before); + } + + #[test] + fn a_design_without_the_signature_is_not_touched_at_open() { + let (p, link) = fake(); + drop(p); + link.borrow_mut().signature = 0; + let before = link.borrow().lite_accesses; + let lite = Bus { link: link.clone(), gpio: false }; + let gpio = Bus { link: link.clone(), gpio: true }; + assert!(AxiPort::::attach(lite, gpio, None).is_err()); + assert_eq!(link.borrow().lite_accesses, before); + } + + #[test] + fn a_write_back_that_wrote_nothing_is_caught() { + let (mut p, link) = fake(); + set_up(&mut p); + p.batch(&chains(4), Duration::from_secs(1)).unwrap(); + // the page still holds the last batch's good results; the poison is what shows + link.borrow_mut().wb_writes_nothing = true; + let e = p.batch(&chains(4), Duration::from_secs(1)).expect_err("stale results"); + assert!(e.0.contains("still holds"), "{}", e.0); + } + + #[test] + fn a_write_back_out_of_place_changes_the_result() { + let (mut p, _) = fake(); + set_up(&mut p); + let good = p.batch(&chains(4), Duration::from_secs(1)).unwrap(); + let (mut q, link) = fake(); + set_up(&mut q); + link.borrow_mut().wb_swaps_chains = true; + assert_ne!(good, q.batch(&chains(4), Duration::from_secs(1)).unwrap()); + } + + #[test] + fn a_page_that_moved_during_a_transfer_is_an_error() { + let (mut p, _) = fake(); + set_up(&mut p); + let d = p.dma.as_ref().unwrap(); + d.reads.set(0); + d.moves.set(true); + let e = p.batch(&chains(4), Duration::from_secs(1)).expect_err("a moved page"); + assert!(e.0.contains("moved"), "{}", e.0); + } + + #[test] + fn a_short_sequencer_run_is_an_error() { + let (mut p, link) = fake(); + set_up(&mut p); + link.borrow_mut().seq_loses_one = true; + let e = p.batch(&chains(4), Duration::from_secs(1)).expect_err("a chain short"); + assert!(e.0.contains("the sequencer ran"), "{}", e.0); + } + + #[test] + fn a_dropped_access_is_an_error() { + let (mut p, link) = fake(); + set_up(&mut p); + link.borrow_mut().drops = 1; + let e = p.batch(&chains(2), Duration::from_secs(1)).expect_err("a dropped access"); + assert!(e.0.contains("dropped"), "{}", e.0); + } + + #[test] + fn the_link_s_port_widths_are_all_three() { + assert!(ports_match(0xa00, 0x3b0, 0x3b0)); + assert!(!ports_match(0x000, 0x3b0, 0x3b0), "the master at 32 bits"); + assert!(!ports_match(0xa00, 0x3b1, 0x3b0), "reads at 64 bits"); + assert!(!ports_match(0xa00, 0x3b0, 0x3b1), "writes at 64 bits"); } } diff --git a/wai/wai-quantum-landscape-fpga/src/board.rs b/wai/wai-quantum-landscape-fpga/src/board.rs index 2458d6b62..9aa6e1a1a 100644 --- a/wai/wai-quantum-landscape-fpga/src/board.rs +++ b/wai/wai-quantum-landscape-fpga/src/board.rs @@ -33,27 +33,40 @@ fn ancestors() -> Vec { out } -/// Processes that, by the board's written rules, mean another session is flashing or measuring. -pub fn foreign_sessions() -> String { - // Override the patterns for another board's habits: WAI_QUANTUM_LANDSCAPE_FPGA_FOREIGN, an extended regex. - let pat = std::env::var("WAI_QUANTUM_LANDSCAPE_FPGA_FOREIGN").unwrap_or_else(|_| "kv_|flashv|flash8|flashfull|ofpga|fpgautil|aba_|wca_".into()); - let out = Command::new("sh") - .arg("-c") - .arg(format!("pgrep -af '{pat}' | grep -v -e pgrep -e wai-quantum-landscape-fpga || true")) - .output(); - let mine = ancestors(); - out.map(|o| { - String::from_utf8_lossy(&o.stdout) - .lines() - .filter(|l| l.split_whitespace().next().and_then(|p| p.parse::().ok()).is_none_or(|p| !mine.contains(&p))) - .collect::>() - .join("\n") - }) - .unwrap_or_default() +/// What counts as another session: this tool, the two scripts that drive it, and the vendor's +/// loader. A site adds its own in `WAI_QUANTUM_LANDSCAPE_FPGA_FOREIGN`, an extended regex: on a +/// shared board, the names the other sessions' tools go by. +pub fn foreign_pattern(site: Option<&str>) -> String { + let own = "wai-quantum-landscape-fpga|run_all\\.sh|power_campaign\\.sh|fpgautil"; + match site { + Some(s) if !s.is_empty() => format!("{own}|{s}"), + _ => own.to_string(), + } } -/// Loads a bitstream through the FPGA manager and proves the load by its dmesg line. -pub fn load_bitstream(path: &str) -> Result { +/// The lines of `pgrep -af` that are another session's: not the search itself, and not this process +/// or anything it descends from. Another run of this tool counts: its lock does not stop a reload +/// by something else, and a queued job is still a job. +pub fn foreign_lines(pgrep: &str, mine: &[u32]) -> String { + pgrep + .lines() + .filter(|l| !l.contains("pgrep")) + .filter(|l| l.split_whitespace().next().and_then(|p| p.parse::().ok()).is_none_or(|p| !mine.contains(&p))) + .collect::>() + .join("\n") +} + +/// Processes that mean another session is flashing or measuring. +pub fn foreign_sessions() -> String { + let pat = foreign_pattern(std::env::var("WAI_QUANTUM_LANDSCAPE_FPGA_FOREIGN").ok().as_deref()); + let out = Command::new("pgrep").arg("-af").arg(&pat).output(); + let mine = ancestors(); + out.map(|o| foreign_lines(&String::from_utf8_lossy(&o.stdout), &mine)).unwrap_or_default() +} + +/// Loads a bitstream through the FPGA manager and proves the load by its dmesg line. The caller +/// holds [`lock`]: a reload must wait for the job that holds the board. +pub fn load_bitstream(path: &str, _held: &Lock) -> Result { let f = foreign_sessions(); if !f.is_empty() { return err(format!("another session is active on the board, not loading:\n{f}")); @@ -140,3 +153,48 @@ pub fn lock() -> Result { } Ok(Lock(f)) } + +#[cfg(test)] +mod tests { + use super::*; + + const PGREP: &str = "101 sudo ./wai-quantum-landscape-fpga conform\n102 ./wai-quantum-landscape-fpga conform\n200 sh power_campaign.sh out\n300 ./wai-quantum-landscape-fpga load x.bit.bin\n301 pgrep -af wai-quantum-landscape-fpga"; + + #[test] + fn another_run_of_this_tool_is_seen() { + let f = foreign_lines(PGREP, &[300]); + assert!(f.contains("102 ./wai-quantum-landscape-fpga conform"), "{f}"); + assert!(f.contains("200 sh power_campaign.sh"), "{f}"); + } + + #[test] + fn this_process_its_parents_and_the_search_are_not() { + let f = foreign_lines(PGREP, &[300, 200, 101, 102]); + assert_eq!(f, ""); + } + + #[test] + fn the_pattern_names_this_tool_whatever_a_site_adds() { + assert!(foreign_pattern(None).contains("wai-quantum-landscape-fpga")); + let p = foreign_pattern(Some("their_tool")); + assert!(p.contains("wai-quantum-landscape-fpga") && p.ends_with("|their_tool"), "{p}"); + } + + #[test] + fn the_lock_serialises_two_holders() { + // two opens of one lock file in one process: the second must wait for the first to drop + use std::sync::mpsc::channel; + use std::time::Duration; + let first = lock().unwrap(); + let (tx, rx) = channel(); + let t = std::thread::spawn(move || { + let l = lock().unwrap(); + tx.send(()).unwrap(); + drop(l); + }); + assert!(rx.recv_timeout(Duration::from_millis(200)).is_err(), "the second holder did not wait"); + drop(first); + rx.recv_timeout(Duration::from_secs(5)).unwrap(); + t.join().unwrap(); + } +} diff --git a/wai/wai-quantum-landscape-fpga/src/conform.rs b/wai/wai-quantum-landscape-fpga/src/conform.rs index 1d91e75d1..6cf41357e 100644 --- a/wai/wai-quantum-landscape-fpga/src/conform.rs +++ b/wai/wai-quantum-landscape-fpga/src/conform.rs @@ -126,7 +126,7 @@ pub fn full(m: &BoardMember

, scale: f64) -> Result { rep.line("wai.quantum.landscape/1 member tests (quantum-conformance/landscape/vectors.json)"); rep.text.push_str(&b.text); rep.check(b.settle_ok, "settle kernel: the settle cases, bit for bit (state, sweeps, ending, accumulates), each held to the job"); - rep.check(b.chains_ok, "sample kernel: the chain vectors, bit for bit (state, sweeps, ending, accumulates, draws), each held to the job"); + rep.check(b.chains_ok, "sample kernel: the chain vectors, bit for bit (the state from the device; sweeps, ending, accumulates and draws by the schedule, as check_chain fixes them), each held to the job"); // the cut points the host gives the engine, against the set's let cells = f["cells"].as_array().unwrap(); diff --git a/wai/wai-quantum-landscape-fpga/src/engine.rs b/wai/wai-quantum-landscape-fpga/src/engine.rs index ffa2272fa..2c93a20a4 100644 --- a/wai/wai-quantum-landscape-fpga/src/engine.rs +++ b/wai/wai-quantum-landscape-fpga/src/engine.rs @@ -94,8 +94,17 @@ pub struct Engine { n: usize, loaded: Option<[u8; 32]>, table: Option<[u8; 32]>, + /// The uptime counter when last read, and what BOUND last held: see [`Engine::alive`]. + uptime: u64, + bound: u32, } +/// What BOUND holds from [`Engine::open`] until the first job: any value but the 0 a reload leaves. +const BOUND_AT_OPEN: u32 = 0x4C41_4E44; +/// The uptime counter is 48 bits. It may wrap inside this much of its top, and not be a reload. +const UPTIME_TOP: u64 = 1 << 48; +const UPTIME_WRAP: u64 = 1 << 36; + impl Engine

{ /// Opens the engine. Refuses anything whose ID and version are not this design's: on a shared /// board that is how a foreign bitstream is caught before it is driven. @@ -111,7 +120,42 @@ impl Engine

{ if v != VERSION_VALUE { return err(format!("engine version {v:#010x}, this host speaks {VERSION_VALUE:#010x}")); } - Ok(Engine { port, timeout: Duration::from_secs(60), n: 0, loaded: None, table: None }) + port.write(BOUND, BOUND_AT_OPEN)?; + let mut e = Engine { port, timeout: Duration::from_secs(60), n: 0, loaded: None, table: None, uptime: 0, bound: BOUND_AT_OPEN }; + e.uptime = e.counter(UPTIME)?; + Ok(e) + } + + /// Whether the engine is still the one this session loaded. A reload of the same design passes + /// the link's signature and the engine's ID, and leaves W, the fields, the cost and the table + /// empty while the keys here still say they are loaded, so a job would run on a wiped engine and + /// give states that look valid. A reload shows two ways: the uptime counter starts again, and + /// BOUND goes back to 0. Checked before anything is taken as loaded and after every job: a + /// result read across a reload is refused, not kept. After a refusal nothing is taken as loaded. + pub fn alive(&mut self) -> Result<(), Error> { + let u = self.counter(UPTIME)?; + let b = self.port.read(BOUND)?; + let wrapped = self.uptime >= UPTIME_TOP - UPTIME_WRAP && u < UPTIME_WRAP; + if (u < self.uptime && !wrapped) || (self.bound != 0 && b != self.bound) { + let was = (self.uptime, self.bound); + self.loaded = None; + self.table = None; + self.n = 0; + self.uptime = u; + self.bound = b; + return err(format!( + "the engine was reloaded under this session (uptime {} -> {u}, BOUND {:#x} -> {b:#x}): its workload and table are gone, and this job's result is not kept", + was.0, was.1 + )); + } + self.uptime = u; + Ok(()) + } + + fn set_bound(&mut self, v: u32) -> Result<(), Error> { + self.port.write(BOUND, v)?; + self.bound = v; + Ok(()) } pub fn n(&self) -> usize { @@ -135,6 +179,7 @@ impl Engine

{ if !(0..=COST_MAX).contains(&w.cost) { return err(format!("the cost {} is beyond the engine's 0 to {COST_MAX}", w.cost)); } + self.alive()?; let key = w.key(); if self.loaded == Some(key) { self.n = w.n; @@ -185,6 +230,7 @@ impl Engine

{ } } let key = *h.finalize().as_bytes(); + self.alive()?; let mut list = vec![(WIN_LO, (win_lo as u32) & 0x3F_FFFF), (WBITS, wbits), (LEVELS_M1, (levels.len() - 1) as u32)]; if self.table == Some(key) { return self.port.bulk(&list); @@ -262,6 +308,7 @@ impl Engine

{ fn finish(&mut self) -> Result<(), Error> { self.port.wait_idle(self.timeout)?; + self.alive()?; let st = self.port.read(STATUS)?; if st & 2 == 0 { return err(format!("the job stopped without finishing (status {st:#x})")); @@ -272,7 +319,7 @@ impl Engine

{ /// The settle kernel: every cell at once, to a fixed point, a two-cycle or the bound. pub fn run_t0(&mut self, start: &[i8], bound: u32) -> Result { self.port.write(MODE, MODE_T0)?; - self.port.write(BOUND, bound)?; + self.set_bound(bound)?; self.set_boundary(start)?; self.port.write(CONTROL, CTL_CLRCNT)?; self.port.write(CONTROL, CTL_START)?; @@ -300,7 +347,7 @@ impl Engine

{ return err("CONT needs at least one sweep"); } self.port.write(MODE, mode | if final_t0 { MODE_FINAL_T0 } else { 0 })?; - self.port.write(BOUND, sweeps)?; + self.set_bound(sweeps)?; self.port.write(SPL, spl.max(1))?; self.port.write(CONTROL, CTL_CLRCNT)?; self.port.write(CONTROL, CTL_CONT)?; @@ -317,7 +364,7 @@ impl Engine

{ pub fn prepare(&mut self, mode: u32, start: &[i8], sweeps: u32, spl: u32, final_t0: bool) -> Result<(), Error> { self.port.write(MODE, mode | if final_t0 { MODE_FINAL_T0 } else { 0 })?; - self.port.write(BOUND, sweeps)?; + self.set_bound(sweeps)?; self.port.write(SPL, spl.max(1))?; self.set_boundary(start) } @@ -326,7 +373,7 @@ impl Engine

{ /// [`Self::halt`]. A sampling job reloads its counter, so every repeat is the same chain. pub fn run_repeat(&mut self, mode: u32, bound: u32, spl: u32, final_t0: bool) -> Result<(), Error> { self.port.write(MODE, mode | MODE_REPEAT | if final_t0 { MODE_FINAL_T0 } else { 0 })?; - self.port.write(BOUND, bound)?; + self.set_bound(bound)?; self.port.write(SPL, spl.max(1))?; self.port.write(CONTROL, CTL_CLRCNT)?; self.port.write(CONTROL, CTL_START) @@ -336,7 +383,9 @@ impl Engine

{ /// for each, its start and the draw counter's start, `x0`. On the AXI link the chains run in /// fabric, one after another, with nothing from the processor between them. pub fn run_batch(&mut self, mode: u32, bound: u32, spl: u32, final_t0: bool, chains: &[(Vec, u64)]) -> Result, Error> { + self.alive()?; self.port.bulk(&[(MODE, mode | if final_t0 { MODE_FINAL_T0 } else { 0 }), (BOUND, bound), (SPL, spl.max(1))])?; + self.bound = bound; let mut words = Vec::with_capacity(chains.len()); for (start, x0) in chains { if start.len() != self.n { @@ -345,8 +394,13 @@ impl Engine

{ let b = encode(start); words.push([b[0], b[1], b[2], b[3], *x0 as u32, (*x0 >> 32) as u32]); } + let results = self.port.batch(&words, self.timeout)?; + self.alive()?; + if results.len() != words.len() { + return err(format!("{} results for a batch of {} chains", results.len(), words.len())); + } let mut out = Vec::with_capacity(chains.len()); - for r in self.port.batch(&words, self.timeout)? { + for r in results { if r[5] & 2 == 0 { return err(format!("a chain of the batch stopped without finishing (status {:#x})", r[5])); } @@ -361,3 +415,121 @@ impl Engine

{ self.port.write(MODE, m & !MODE_REPEAT) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::model::ModelPort; + use crate::port::{ChainWords, ResultWords}; + + fn workload() -> Loaded { + Loaded { n: 3, rows: vec![vec![(1, 1)], vec![(0, 1), (2, -1)], vec![(1, -1)]], fields: vec![2, 0, -1], cost: 1 } + } + + /// The same design loaded again, as a port sees it: every register back at its reset value. + fn reload(e: &mut Engine) { + e.port = ModelPort::new(); + } + + #[test] + fn a_reload_between_jobs_stops_the_next_job() { + let w = workload(); + let mut e = Engine::open(ModelPort::new()).unwrap(); + e.load(&w).unwrap(); + let good = e.run_t0(&[1, 0, -1], 20).unwrap(); + reload(&mut e); + let refused = e.load(&w).expect_err("a cached workload on a wiped engine"); + assert!(refused.0.contains("reloaded"), "{}", refused.0); + // nothing is taken as loaded now: the workload goes back in, and the job gives the same bits + e.load(&w).unwrap(); + assert_eq!(e.run_t0(&[1, 0, -1], 20).unwrap().state, good.state); + } + + #[test] + fn a_reload_shows_by_bound_where_uptime_has_passed_its_old_value() { + let w = workload(); + let mut e = Engine::open(ModelPort::new()).unwrap(); + e.load(&w).unwrap(); + e.run_t0(&[1, 0, -1], 20).unwrap(); + reload(&mut e); + for _ in 0..1000 { + e.port.read(ID).unwrap(); // the fresh engine's uptime counts past the old one's + } + assert!(e.load(&w).is_err()); + } + + #[test] + fn a_table_is_not_taken_as_loaded_across_a_reload() { + let levels = vec![vec![(1u64, 2u64); 4]]; + let mut e = Engine::open(ModelPort::new()).unwrap(); + e.load(&workload()).unwrap(); + e.load_table(0, 2, 0, &levels).unwrap(); + reload(&mut e); + assert!(e.load_table(0, 2, 0, &levels).is_err()); + } + + /// A port whose engine is reloaded while a job runs. + struct ReloadedMidJob(ModelPort); + impl Port for ReloadedMidJob { + fn write(&mut self, a: u16, v: u32) -> Result<(), Error> { + self.0.write(a, v)?; + if a == CONTROL && v & CTL_START != 0 { + self.0 = ModelPort::new(); + } + Ok(()) + } + fn read(&mut self, a: u16) -> Result { + self.0.read(a) + } + fn wait_idle(&mut self, t: Duration) -> Result<(), Error> { + self.0.wait_idle(t) + } + } + + #[test] + fn a_result_read_across_a_reload_is_refused() { + let mut e = Engine::open(ReloadedMidJob(ModelPort::new())).unwrap(); + e.load(&workload()).unwrap(); + assert!(e.run_t0(&[1, 0, -1], 20).is_err()); + let mut e = Engine::open(ReloadedMidJob(ModelPort::new())).unwrap(); + e.load(&workload()).unwrap(); + assert!(e.run_batch(MODE_T0, 20, 1, false, &[(vec![1, 0, -1], 0)]).is_err()); + } + + #[test] + fn the_uptime_counter_wrapping_is_not_a_reload() { + let mut e = Engine::open(ModelPort::new()).unwrap(); + e.uptime = UPTIME_TOP - 5; + e.alive().unwrap(); + e.uptime = UPTIME_TOP / 2; + assert!(e.alive().is_err()); + } + + /// A port that loses the last result of every batch. + struct ShortBatch(ModelPort); + impl Port for ShortBatch { + fn write(&mut self, a: u16, v: u32) -> Result<(), Error> { + self.0.write(a, v) + } + fn read(&mut self, a: u16) -> Result { + self.0.read(a) + } + fn wait_idle(&mut self, t: Duration) -> Result<(), Error> { + self.0.wait_idle(t) + } + fn batch(&mut self, chains: &[ChainWords], t: Duration) -> Result, Error> { + let mut out = crate::port::batch_plain(self, chains, t)?; + out.pop(); + Ok(out) + } + } + + #[test] + fn a_short_batch_is_refused() { + let mut e = Engine::open(ShortBatch(ModelPort::new())).unwrap(); + e.load(&workload()).unwrap(); + let chains = vec![(vec![1, 0, -1], 0), (vec![0, 0, 0], 0)]; + let refused = e.run_batch(MODE_T0, 20, 1, false, &chains).expect_err("a batch that came back short"); + assert!(refused.0.contains("results for a batch"), "{}", refused.0); + } +} diff --git a/wai/wai-quantum-landscape-fpga/src/exact.rs b/wai/wai-quantum-landscape-fpga/src/exact.rs index e47602658..e0a4a27f5 100644 --- a/wai/wai-quantum-landscape-fpga/src/exact.rs +++ b/wai/wai-quantum-landscape-fpga/src/exact.rs @@ -377,9 +377,9 @@ pub fn run_case(e: &mut Engine

, c: &Case, seed: u64) -> Result(e: &mut Engine

, scale: f64, seed: u64) -> Result { let mut rep = crate::conform::Report::default(); rep.line(format!("joint statistics against exact enumeration (chains scaled by {scale}, seed {seed})")); @@ -391,7 +391,7 @@ pub fn report(e: &mut Engine

, scale: f64, seed: u64) -> Result(e: &mut Engine

, scale: f64, seed: u64) -> Result= 1e-3 && f.p_g >= 1e-3 && f.p_cell >= 1e-3 && f.p_pair >= 1e-3; rep.check(ok, format!("{}: the joint law is the exact law (every p at least 0.001)", c.name)); - rep.check(o.same == f.samples, format!("{}: every chain's final state is the reference's", c.name)); + rep.check(o.same == c.chains && f.samples == c.chains, format!("{}: every chain requested came back, its final state the reference's", c.name)); if scale >= 1.0 { - rep.check(o.control.p_chi2 < 1e-6, format!("{}: the independent-cells control is rejected (chi-square p below 1e-6)", c.name)); + rep.check(o.control.p_chi2 < 1e-6 && o.control.p_cell >= 1e-3, format!("{}: the independent-cells control is rejected (chi-square p below 1e-6) and its cell family passes", c.name)); } } Ok(rep) @@ -454,6 +454,34 @@ mod tests { assert!(ctl.p_cell > 1e-3, "the control has the right marginals: {ctl:?}"); } + /// Rule 3 counts against the chains requested: a run that lost some is not a pass. + #[test] + fn a_run_that_lost_chains_fails_the_report() { + use crate::engine::Engine; + use crate::model::ModelPort; + use crate::port::{ChainWords, Error, Port, ResultWords, batch_plain}; + use std::time::Duration; + struct Lossy(ModelPort); + impl Port for Lossy { + fn write(&mut self, a: u16, v: u32) -> Result<(), Error> { + self.0.write(a, v) + } + fn read(&mut self, a: u16) -> Result { + self.0.read(a) + } + fn wait_idle(&mut self, t: Duration) -> Result<(), Error> { + self.0.wait_idle(t) + } + fn batch(&mut self, chains: &[ChainWords], t: Duration) -> Result, Error> { + let mut out = batch_plain(self, chains, t)?; + out.pop(); + Ok(out) + } + } + let mut e = Engine::open(Lossy(ModelPort::new())).unwrap(); + assert!(report(&mut e, 0.002, 1).is_err()); + } + #[test] fn the_tails_are_right() { // chi-square with 10 degrees of freedom: P(X > 18.307) = 0.05; with 1000: P(X > 1074.68) = 0.05 diff --git a/wai/wai-quantum-landscape-fpga/src/latency.rs b/wai/wai-quantum-landscape-fpga/src/latency.rs index 6a7d87813..27cf59abe 100644 --- a/wai/wai-quantum-landscape-fpga/src/latency.rs +++ b/wai/wai-quantum-landscape-fpga/src/latency.rs @@ -141,8 +141,8 @@ pub fn bulk_paths(e: &mut Engine

, label: &str, chains: u64) -> Resul Ok(s) } -#[cfg(unix)] -pub fn axi(p: &mut crate::axi::AxiPort, n: usize) -> String { +#[cfg(target_os = "linux")] +pub fn axi(p: &mut crate::axi::BoardAxi, n: usize) -> String { use crate::axi::*; let mut s = header("AXI"); s.push_str(&time_each(n, || { @@ -212,7 +212,7 @@ pub fn axi(p: &mut crate::axi::AxiPort, n: usize) -> String { s } -#[cfg(unix)] +#[cfg(target_os = "linux")] pub fn emio(p: &mut crate::emio::EmioPort, n: usize) -> String { let mut s = header("EMIO"); s.push_str(&time_each(n, || { @@ -227,3 +227,25 @@ pub fn emio(p: &mut crate::emio::EmioPort, n: usize) -> String { s.push('\n'); s } + +#[cfg(test)] +mod tests { + use super::*; + use crate::model::ModelPort; + + /// The chain `bulk_paths` times, in the engine's own clocks: 4 a cell update, 8 cells, 4 sweeps, + /// 9 to prime the draws and 2 to start and end. The model counts them as the logic does: the + /// testbenches hold every read of CYCLES to it. The README's figure for the engine's share of a + /// chain is this count. + #[test] + fn the_timed_chain_keeps_the_engine_busy_this_many_clocks() { + let c = cases(1.0).remove(0); + let l = lowered(&c.landscape.lower()); + let seg = plan(&l, &c.temps).unwrap(); + let mut e = Engine::open(ModelPort::new()).unwrap(); + e.load(&l).unwrap(); + e.port.write(CONTROL, CTL_CLRCNT).unwrap(); + run_schedule(&mut e, &l, &seg, false, &[(c.start.clone(), 1)], 0, MODE_GIBBS).unwrap(); + assert_eq!(e.counter(CYCLES).unwrap(), 4 * 8 * 4 + 9 + 2); + } +} diff --git a/wai/wai-quantum-landscape-fpga/src/lib.rs b/wai/wai-quantum-landscape-fpga/src/lib.rs index b64b3ac7b..234dc032e 100644 --- a/wai/wai-quantum-landscape-fpga/src/lib.rs +++ b/wai/wai-quantum-landscape-fpga/src/lib.rs @@ -15,9 +15,10 @@ pub mod port; pub mod regs; pub mod script; -#[cfg(unix)] -pub mod board; -#[cfg(unix)] -pub mod emio; -#[cfg(unix)] pub mod power; + +// The board's own side: Linux's /dev/mem, its constants, and the kernel's FPGA manager. +#[cfg(target_os = "linux")] +pub mod board; +#[cfg(target_os = "linux")] +pub mod emio; diff --git a/wai/wai-quantum-landscape-fpga/src/main.rs b/wai/wai-quantum-landscape-fpga/src/main.rs index 8fd42c171..d2fe8bde4 100644 --- a/wai/wai-quantum-landscape-fpga/src/main.rs +++ b/wai/wai-quantum-landscape-fpga/src/main.rs @@ -1,16 +1,21 @@ //! wai-quantum-landscape-fpga: the command line. Board commands run on the board's own Linux, as root (for -//! /dev/mem). `selftest`, `gen-tb` and `analyze` run anywhere. +//! /dev/mem). `selftest`, `gen-tb`, `analyze`, `joint --model` and `landscape --model` run anywhere. use serde_json::Value; +#[cfg(target_os = "linux")] use std::fmt::Write as _; use std::process::exit; use wai_quantum::quantum_landscape::energy::Landscape; use wai_quantum::quantum_landscape::job; use wai_quantum_landscape_fpga::conform::{self, Report}; -use wai_quantum_landscape_fpga::engine::{Engine, Loaded}; +use wai_quantum_landscape_fpga::engine::Engine; +#[cfg(target_os = "linux")] +use wai_quantum_landscape_fpga::engine::Loaded; use wai_quantum_landscape_fpga::member::{BoardInfo, BoardMember, lowered, plan}; use wai_quantum_landscape_fpga::model::ModelPort; -use wai_quantum_landscape_fpga::port::{Error, Port}; +use wai_quantum_landscape_fpga::port::Port; +#[cfg(target_os = "linux")] +use wai_quantum_landscape_fpga::port::Error; use wai_quantum_landscape_fpga::regs::*; use wai_quantum_landscape_fpga::script::ScriptPort; @@ -38,7 +43,9 @@ fn usage() -> ! { } fn flag(args: &[String], name: &str) -> Option { - args.iter().position(|a| a == name).and_then(|i| args.get(i + 1)).and_then(|v| v.parse().ok()) + let i = args.iter().position(|a| a == name)?; + let v = args.get(i + 1).unwrap_or_else(|| die(format!("{name} needs a value"))); + Some(v.parse().unwrap_or_else(|_| die(format!("{name}: `{v}` is not a value it takes")))) } fn die(e: impl std::fmt::Display) -> ! { @@ -82,7 +89,7 @@ fn recall() -> (Landscape, Vec) { } fn model_member() -> BoardMember { - let mut m = BoardMember::new(Engine::open(ModelPort::new()).unwrap_or_else(|e| die(e)), BoardInfo { name: "model".into(), description: "the register-level model of the engine, in software".into() }); + let mut m = BoardMember::new(Engine::open(ModelPort::new()).unwrap_or_else(|e| die(e)), model_info()); let t = m.earn().unwrap_or_else(|e| die(e)); print!("{t}"); m @@ -130,10 +137,17 @@ fn key(args: &[String]) -> Option { Some(SigningKey::from_bytes(&seed)) } +/// The seed the joint-statistics test is registered at (`measurements/2026-11-06/JOINT-PREREG.txt`). +const JOINT_SEED: u64 = 2027; + +fn model_info() -> BoardInfo { + BoardInfo { name: "model".into(), description: "the register-level model of the engine, in software".into() } +} + /// The board commands, on any port: the whole board, or one core of the multi-core bitstream. -fn on_board(cmd: &str, args: &[String], port: P) { +fn on_board(cmd: &str, args: &[String], port: P, info: BoardInfo) { let engine = Engine::open(port).unwrap_or_else(|e| die(e)); - let mut m = BoardMember::new(engine, BoardInfo::default()); + let mut m = BoardMember::new(engine, info); match cmd { "conform" => { let mut rep = conform::full(&m, flag(args, "--scale").unwrap_or(1.0)).unwrap_or_else(|e| die(e)); @@ -141,8 +155,8 @@ fn on_board(cmd: &str, args: &[String], port: P) { rep.text += &f.text; rep.checks += f.checks; rep.failures.extend(f.failures); - let ok = m.engine.borrow_mut().port.read(ID).map(|v| v == ID_VALUE).unwrap_or(false); - rep.check(ok, "the engine still answers with its ID at the end: no other session replaced it"); + let ok = m.engine.borrow_mut().alive().is_ok(); + rep.check(ok, "the engine was not reloaded during the run: no other session replaced it"); finish(&rep) } "agree" => { @@ -176,11 +190,11 @@ fn on_board(cmd: &str, args: &[String], port: P) { } "joint" => { let mut e = m.engine.borrow_mut(); - let rep = wai_quantum_landscape_fpga::exact::report(&mut e, flag(args, "--scale").unwrap_or(1.0), flag(args, "--seed").unwrap_or(2026)).unwrap_or_else(|x| die(x)); + let rep = wai_quantum_landscape_fpga::exact::report(&mut e, flag(args, "--scale").unwrap_or(1.0), flag(args, "--seed").unwrap_or(JOINT_SEED)).unwrap_or_else(|x| die(x)); drop(e); - let ok = m.engine.borrow_mut().port.read(ID).map(|v| v == ID_VALUE).unwrap_or(false); + let ok = m.engine.borrow_mut().alive().is_ok(); let mut rep = rep; - rep.check(ok, "the engine still answers with its ID at the end: no other session replaced it"); + rep.check(ok, "the engine was not reloaded during the run: no other session replaced it"); finish(&rep) } "status" => { @@ -217,7 +231,7 @@ fn on_board(cmd: &str, args: &[String], port: P) { } } -#[cfg(unix)] +#[cfg(target_os = "linux")] fn meter_class(e: &mut Engine

, class: &str, plan_m: &wai_quantum_landscape_fpga::power::Plan, log: &mut String) -> Vec { let (l, cue) = recall(); let lw = lowered(&l.lower()); @@ -248,15 +262,15 @@ fn meter_class(e: &mut Engine

, class: &str, plan_m: &wai_quantum_lan wai_quantum_landscape_fpga::power::meter(e, plan_m, &mut start, log).unwrap_or_else(|x| die(x)) } -#[cfg(unix)] +#[cfg(target_os = "linux")] enum Link { - Axi(wai_quantum_landscape_fpga::axi::AxiPort), + Axi(wai_quantum_landscape_fpga::axi::BoardAxi), Emio(wai_quantum_landscape_fpga::emio::EmioPort), } /// The loaded design's link: AXI if its signature is on EMIO bank 3 (a GPIO read, safe whatever is /// loaded), else EMIO; `--link` insists on one. -#[cfg(unix)] +#[cfg(target_os = "linux")] fn open_link(args: &[String]) -> Link { let axi = match flag::(args, "--link").as_deref() { Some("axi") => true, @@ -265,13 +279,13 @@ fn open_link(args: &[String]) -> Link { None => wai_quantum_landscape_fpga::axi::signature_present(), }; if axi { - Link::Axi(wai_quantum_landscape_fpga::axi::AxiPort::open().unwrap_or_else(|e| die(e))) + Link::Axi(wai_quantum_landscape_fpga::axi::open().unwrap_or_else(|e| die(e))) } else { Link::Emio(wai_quantum_landscape_fpga::emio::EmioPort::open().unwrap_or_else(|e| die(e))) } } -#[cfg(unix)] +#[cfg(target_os = "linux")] macro_rules! with_link { ($args:expr, $p:ident => $body:expr) => { match open_link($args) { @@ -289,7 +303,7 @@ fn main() { let scale = flag(&args, "--scale").unwrap_or(0.02); let m = model_member(); let mut rep = conform::full(&m, scale).unwrap_or_else(|e| die(e)); - let joint = wai_quantum_landscape_fpga::exact::report(&mut m.engine.borrow_mut(), 0.002, 2026).unwrap_or_else(|e| die(e)); + let joint = wai_quantum_landscape_fpga::exact::report(&mut m.engine.borrow_mut(), 0.002, JOINT_SEED).unwrap_or_else(|e| die(e)); for r in [conform::fields(&m, 4).unwrap_or_else(|e| die(e)), conform::agree(&m, flag(&args, "--seeds").unwrap_or(3)).unwrap_or_else(|e| die(e)), joint] { rep.text += &r.text; rep.checks += r.checks; @@ -306,40 +320,47 @@ fn main() { std::fs::write(out, &e.port.text).unwrap_or_else(|e| die(e)); println!("{} lines, {} checked reads -> {out}", e.port.text.lines().count(), e.port.reads); } + "landscape" if args.iter().any(|a| a == "--model") => on_board(cmd, &args, ModelPort::new(), model_info()), "joint" if args.iter().any(|a| a == "--model") => { let mut e = Engine::open(ModelPort::new()).unwrap_or_else(|e| die(e)); - let rep = wai_quantum_landscape_fpga::exact::report(&mut e, flag(&args, "--scale").unwrap_or(1.0), flag(&args, "--seed").unwrap_or(2026)).unwrap_or_else(|e| die(e)); + let rep = wai_quantum_landscape_fpga::exact::report(&mut e, flag(&args, "--scale").unwrap_or(1.0), flag(&args, "--seed").unwrap_or(JOINT_SEED)).unwrap_or_else(|e| die(e)); finish(&rep) } "analyze" => { let f = args.get(1).unwrap_or_else(|| usage()); let text = std::fs::read_to_string(f).unwrap_or_else(|e| die(e)); - #[cfg(unix)] - { - let pts = wai_quantum_landscape_fpga::power::parse_log(&text); - print!("{}", wai_quantum_landscape_fpga::power::summarise(&pts, flag(&args, "--predict")).text); - } - let _ = text; + let pts = wai_quantum_landscape_fpga::power::parse_log(&text); + print!("{}", wai_quantum_landscape_fpga::power::summarise(&pts, flag(&args, "--predict")).text); } - #[cfg(unix)] + #[cfg(target_os = "linux")] "load" => { let f = args.get(1).unwrap_or_else(|| usage()); - println!("{}", wai_quantum_landscape_fpga::board::load_bitstream(f).unwrap_or_else(|e| die(e))); - let _l = wai_quantum_landscape_fpga::board::lock().unwrap_or_else(|e| die(e)); + // the lock first: a reload waits for the job that holds the board + let l = wai_quantum_landscape_fpga::board::lock().unwrap_or_else(|e| die(e)); + println!("{}", wai_quantum_landscape_fpga::board::load_bitstream(f, &l).unwrap_or_else(|e| die(e))); let link = with_link!(&args, p => { let name = p.name(); Engine::open(p).unwrap_or_else(|e| die(e)); name }); println!("the engine answers with its ID and version, over its {link} link"); } - #[cfg(unix)] + #[cfg(target_os = "linux")] + "foreign" => { + // for the scripts: this tool and its parents are left out, so a script need not know its own + let f = wai_quantum_landscape_fpga::board::foreign_sessions(); + if !f.is_empty() { + println!("{f}"); + exit(3); + } + } + #[cfg(target_os = "linux")] "ps-ports" => { let (ok, text) = wai_quantum_landscape_fpga::axi::ps_ports().unwrap_or_else(|e| die(e)); println!("{text}: {}", if ok { "they match" } else { "they do not match" }); } - #[cfg(unix)] + #[cfg(target_os = "linux")] "release" => { wai_quantum_landscape_fpga::emio::EmioPort::release_to_boot_state().unwrap_or_else(|e| die(e)); println!("EMIO banks 3-5: inputs, outputs disabled, data 0"); } - #[cfg(unix)] + #[cfg(target_os = "linux")] "clock" => { let d: u32 = args.get(1).and_then(|v| v.parse().ok()).unwrap_or_else(|| usage()); let _l = wai_quantum_landscape_fpga::board::lock().unwrap_or_else(|e| die(e)); @@ -354,7 +375,7 @@ fn main() { println!("PL0_REF_CTRL was {old:#x}; nominal {:.2} MHz; measured {:.4} MHz", 1500.0 / f64::from(d), (u1 - u0) as f64 / t0.elapsed().as_secs_f64() / 1e6); }); } - #[cfg(unix)] + #[cfg(target_os = "linux")] "meter" => { let class = args.get(1).cloned().unwrap_or_else(|| usage()); let passes: u32 = args.get(2).and_then(|v| v.parse().ok()).unwrap_or_else(|| usage()); @@ -379,7 +400,7 @@ fn main() { println!("# {l}"); } } - #[cfg(unix)] + #[cfg(target_os = "linux")] "latency" => { let n: usize = args.get(1).and_then(|v| v.parse().ok()).unwrap_or(100_000); let _l = wai_quantum_landscape_fpga::board::lock().unwrap_or_else(|e| die(e)); @@ -401,14 +422,14 @@ fn main() { } } } - #[cfg(unix)] + #[cfg(target_os = "linux")] "conform" | "agree" | "landscape" | "status" | "throughput" | "joint" => { let _l = wai_quantum_landscape_fpga::board::lock().unwrap_or_else(|e| die(e)); match (open_link(&args), flag::(&args, "--core")) { - (Link::Emio(mut port), Some(core)) => on_board(cmd, &args, wai_quantum_landscape_fpga::port::CorePort { inner: &mut port, core }), + (Link::Emio(mut port), Some(core)) => on_board(cmd, &args, wai_quantum_landscape_fpga::port::CorePort { inner: &mut port, core }, BoardInfo::default()), (Link::Axi(_), Some(_)) => die("--core is for the multi-core EMIO build"), - (Link::Axi(p), None) => on_board(cmd, &args, p), - (Link::Emio(p), None) => on_board(cmd, &args, p), + (Link::Axi(p), None) => on_board(cmd, &args, p, BoardInfo::default()), + (Link::Emio(p), None) => on_board(cmd, &args, p, BoardInfo::default()), } } _ => usage(), diff --git a/wai/wai-quantum-landscape-fpga/src/member.rs b/wai/wai-quantum-landscape-fpga/src/member.rs index aab3e503b..f100b1634 100644 --- a/wai/wai-quantum-landscape-fpga/src/member.rs +++ b/wai/wai-quantum-landscape-fpga/src/member.rs @@ -24,6 +24,10 @@ //! at a fixed point or a two-cycle, and past a two-cycle the state alternates, so one more sweep is //! taken when the count left is odd. //! +//! The ending, sweeps, accumulates and draws of a chain's `Outcome` come from the schedule and the +//! state, as the reference's do (`check_chain` fixes them for every member); the engine's own count +//! of sweeps is held to its segment here. +//! //! What it has earned is not assumed, and not said by the member: the host certifies it //! (`wai_quantum::quantum_landscape::member::certify`) by running the conformance set's `settle` //! and `chains` vectors on the device, and, where those miss, the statistical battery. @@ -204,6 +208,10 @@ pub fn run_schedule(e: &mut Engine

, l: &Loaded, segments: &[Segment] let first_draw = done * l.n as u64 + 1; let list: Vec<(Vec, u64)> = chains.iter().zip(&states).map(|(c, st)| (st.clone(), c.1.wrapping_add(GOLDEN.wrapping_mul(first_draw)))).collect(); let runs = e.run_batch(mode, seg.sweeps as u32, 1, last && final_t0, &list).map_err(|e| e.0)?; + // a sampling job has no early end: the engine reports every sweep it was asked for + if let Some(r) = runs.iter().find(|r| r.steps != seg.sweeps as u32) { + return Err(format!("a chain ran {} sweeps of a segment of {}", r.steps, seg.sweeps)); + } states = runs.into_iter().map(|r| r.state).collect(); done += seg.sweeps as u64; } @@ -319,3 +327,119 @@ impl Member for BoardMember

{ .collect()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::model::ModelPort; + use wai_quantum::quantum_landscape::energy::Landscape; + + fn model() -> BoardMember { + BoardMember::new(Engine::open(ModelPort::new()).unwrap(), BoardInfo { name: "model".into(), description: "the model".into() }) + } + + /// Held to the reference on the model, and every field and the cost inside the registers. + fn same_as_reference(m: &BoardMember, l: &Landscape, start: &[i8]) { + let w = l.lower(); + let e = lowered_settle(&w); + assert!(e.cost <= COST_MAX && e.fields.iter().all(|f| f.abs() <= FIELD_MAX), "{e:?}"); + assert_eq!(m.settle(&w, start, 40).unwrap(), w.settle(start, 40)); + } + + fn pair(b0: i64, b1: i64, c: i64) -> Landscape { + let mut l = Landscape::new(4); + l.couple(0, 1, 1).couple(1, 2, -1).couple(2, 3, 1); + l.set_field(0, b0).set_field(1, b1).set_cost(c); + l + } + + #[test] + fn a_cost_too_large_keeps_each_threshold_where_it_matters() { + let m = model(); + let c = 1 << 40; + same_as_reference(&m, &pair(c, 0, c), &[1, 0, -1, 1]); // the upper threshold in reach + same_as_reference(&m, &pair(-c + 1, 0, c), &[1, 0, -1, 1]); // the lower one in reach + same_as_reference(&m, &pair(0, 3, c), &[1, 0, -1, 1]); // neither: always 0 + same_as_reference(&m, &pair(3 * c, 0, c), &[0, 0, 0, 0]); // both below: always +1 + same_as_reference(&m, &pair(-3 * c, 0, c), &[0, 0, 0, 0]); // both above: always -1 + same_as_reference(&m, &pair(2097152, -2097152, 1048576), &[0, 0, 0, 0]); + } + + #[test] + fn a_small_cost_with_a_field_too_large_is_clamped() { + let m = model(); + for c in [0, 1, 7, 64] { + same_as_reference(&m, &pair(1_000_000, -1_000_000, c), &[1, 0, -1, 1]); + same_as_reference(&m, &pair(c + 64 + 1, -1_000_000, c), &[0, 0, 0, 0]); + } + } + + #[test] + fn every_threshold_case_settles_as_the_reference_does() { + let m = model(); + let mut z = 0x001A_46E5u64; + let mut next = move || { + z = z.wrapping_add(GOLDEN); + crate::model::mix(z) + }; + for _ in 0..300 { + let n = 2 + (next() % 63) as usize; + let scale = 1i64 << (next() % 41); + let cost = if next() & 1 == 0 { (next() % 70) as i64 } else { (next() as i64 & i64::MAX) % scale }; + let mut l = Landscape::new(n); + for i in 0..n { + for j in (i + 1)..n { + if next() % 4 == 0 { + l.couple(i, j, if next() & 1 == 0 { 1 } else { -1 }); + } + } + // near a threshold, or anywhere + let b = match next() % 3 { + 0 => cost + (next() % 130) as i64 - 65, + 1 => -cost + (next() % 130) as i64 - 65, + _ => (next() as i64) % scale, + }; + l.set_field(i, b); + } + l.set_cost(cost); + let start: Vec = (0..n).map(|_| (next() % 3) as i8 - 1).collect(); + same_as_reference(&m, &l, &start); + } + } + + #[test] + fn a_workload_inside_the_registers_is_left_alone() { + let w = pair(5, -3, 2).lower(); + assert_eq!(lowered_settle(&w), lowered(&w)); + } + + #[test] + fn sampling_beyond_the_registers_is_refused() { + let m = model(); + let w = pair(1_000_000, 0, 1).lower(); + assert!(m.chains(&w, &[Chain { start: vec![0; 4], seed: 1 }], &[1.0, 0.0]).is_err()); + } + + /// A port that runs every sampled job one sweep short, by what it reports. + struct ShortSweeps(ModelPort); + impl Port for ShortSweeps { + fn write(&mut self, a: u16, v: u32) -> Result<(), crate::port::Error> { + self.0.write(a, v) + } + fn read(&mut self, a: u16) -> Result { + let v = self.0.read(a)?; + Ok(if a == STEPS { v.saturating_sub(1) } else { v }) + } + fn wait_idle(&mut self, t: std::time::Duration) -> Result<(), crate::port::Error> { + self.0.wait_idle(t) + } + } + + #[test] + fn a_chain_that_ran_the_wrong_number_of_sweeps_is_refused() { + let m = BoardMember::new(Engine::open(ShortSweeps(ModelPort::new())).unwrap(), BoardInfo::default()); + let w = pair(1, 0, 1).lower(); + let refused = m.chains(&w, &[Chain { start: vec![0; 4], seed: 1 }], &[1.5, 1.0, 0.5]).expect_err("the count of sweeps"); + assert!(refused.contains("sweeps of a segment"), "{refused}"); + } +} diff --git a/wai/wai-quantum-landscape-fpga/src/power.rs b/wai/wai-quantum-landscape-fpga/src/power.rs index 82936f241..d15f86655 100644 --- a/wai/wai-quantum-landscape-fpga/src/power.rs +++ b/wai/wai-quantum-landscape-fpga/src/power.rs @@ -10,13 +10,19 @@ //! writes must not have moved. A flagged pass is dropped, never averaged in; //! * the statistic is the mean of per-pass differences of medians, its error the standard error //! across passes (n = passes, not samples); -//! * a prediction is written to `predictions/` before the run and printed beside the result. +//! * `--predict` prints a figure beside the result. It is only what the command line said: what +//! makes a prediction pre-registered is the file committed before the run, `measurements/*/`. +#[cfg(target_os = "linux")] use crate::board; +#[cfg(target_os = "linux")] use crate::engine::Engine; +#[cfg(target_os = "linux")] use crate::port::{Error, Port}; +#[cfg(target_os = "linux")] use crate::regs::*; use std::fmt::Write as _; +#[cfg(target_os = "linux")] use std::time::{Duration, Instant, SystemTime}; pub struct Plan { @@ -37,6 +43,7 @@ pub struct Point { pub flagged: Option, } +#[cfg(target_os = "linux")] fn urandom_bit() -> bool { let mut b = [0u8; 1]; use std::io::Read; @@ -47,6 +54,7 @@ fn urandom_bit() -> bool { } /// `start` puts the engine into repeat mode on the prepared job; the meter halts it for the halt slot. +#[cfg(target_os = "linux")] pub fn meter(edge: &mut Engine

, plan: &Plan, start: &mut dyn FnMut(&mut Engine

) -> Result<(), Error>, log: &mut String) -> Result, Error> { let sensor = board::find_power_sensor()?; let mut pts = vec![]; @@ -160,7 +168,7 @@ pub fn summarise(pts: &[Point], predicted_mw: Option) -> Summary { let (pr, _) = mean_sd(&prun); let _ = writeln!(t, "board power while running {pr:.0} mW; jobs/s {j:.1}; updates/s {u:.1}; measured PL clock {:.3} MHz", c / 1e6); if let Some(p) = predicted_mw { - let _ = writeln!(t, "pre-registered prediction {p:.1} mW: measured/predicted = {:.2}, {:.1} sigma from it", m / p, (m - p) / se); + let _ = writeln!(t, "prediction given {p:.1} mW: measured/predicted = {:.2}, {:.1} sigma from it", m / p, (m - p) / se); } Summary { text: t, delta_mw: m, se_mw: se, passes_used: diffs.len(), jobs_per_s: j, updates_per_s: u, clock_hz: c, p_run_mw: pr } } diff --git a/wai/wai-quantum-landscape-fpga/tb/tb_landscape.v b/wai/wai-quantum-landscape-fpga/tb/tb_landscape.v index 6f94561d9..e7553e77b 100644 --- a/wai/wai-quantum-landscape-fpga/tb/tb_landscape.v +++ b/wai/wai-quantum-landscape-fpga/tb/tb_landscape.v @@ -1,5 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -// Replays a session recorded from the host's software model (`vqpu-edge gen-tb`) against the RTL, +// Replays a session recorded from the host's software model (`wai-quantum-landscape-fpga gen-tb landscape_tb.script`) against the RTL, // through the EMIO port and its synchroniser, with the PS side deliberately off the PL clock. // Every read must return what the model returned. A mismatch names the script line. `timescale 1ns/1ps -- 2.54.0 (Apple Git-157) From 39d8a84b298af7c264e5a9faec858ac5dafd633b Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 7 Nov 2026 03:36:31 -0500 Subject: [PATCH 11/11] wai-quantum-landscape-fpga: held to the grown conformance set, and what it cannot hold said Main's set now holds settle cases to 1,000 cells, chains to 1,024 cells, sampled fields to 2^31 - 1 and a battery workload whose uncertain fields span 135,000, and a member may decline only fields above 2^31 - 1. The member declines that and nothing else (takes). What the 64-cell engine cannot hold it refuses with a sentence; member::beyond says why, and the checks hold the board to the reference's bits on every case it holds and to a refusal on every case it does not. On the model: 21/21 settle cases and 227/227 chains held, 13 cases refused, so the host certifies both kernels uncertified under the current set. The README, the changelog and CI say so; the board was certified bit-exact under the set of 2026-11-06, when its measurements were taken. The build record carries the testbenches' results on the new session, in Icarus Verilog and xsim alike. Co-Authored-By: Claude Opus 5.5 --- .../workflows/wai-quantum-landscape-fpga.yml | 11 ++- .../2026-11-06-landscape-fpga-member.md | 2 +- wai/wai-quantum-landscape-fpga/README.md | 36 +++++-- .../measurements/2026-11-06/build.txt | 14 +-- wai/wai-quantum-landscape-fpga/src/conform.rs | 94 ++++++++++++++++--- wai/wai-quantum-landscape-fpga/src/member.rs | 48 ++++++++++ 6 files changed, 170 insertions(+), 35 deletions(-) diff --git a/.github/workflows/wai-quantum-landscape-fpga.yml b/.github/workflows/wai-quantum-landscape-fpga.yml index 6e2f6a463..1fa41fddb 100644 --- a/.github/workflows/wai-quantum-landscape-fpga.yml +++ b/.github/workflows/wai-quantum-landscape-fpga.yml @@ -84,22 +84,27 @@ jobs: cargo test --locked --release 2>&1 | tee "$RUNNER_TEMP/test.log" grep -qE '^test result: ok\. [1-9][0-9]* passed; 0 failed' "$RUNNER_TEMP/test.log" || { echo "::error::no test ran"; exit 1; } cargo clippy --locked --release --all-targets -- -D warnings + # Every case the engine holds must give the reference's bits and every one it cannot must be + # refused (the checks); the host's certification is printed, whatever it is: under the set + # as it stands, cases beyond the engine's 64 cells cost it its certificate (README). - name: The member certified, the conformance set and the worked examples, on the model shell: bash working-directory: wai/wai-quantum-landscape-fpga run: | set -euo pipefail cargo run --locked --release -- selftest --scale 0.05 --seeds 5 | tee "$RUNNER_TEMP/selftest.log" - grep -q 'certified by the host on the conformance set: settle bit-exact, sample bit-exact' "$RUNNER_TEMP/selftest.log" + grep -q 'certified by the host on the conformance set: settle ' "$RUNNER_TEMP/selftest.log" + grep -qE '^settle cases the engine holds: ([0-9]+)/\1 bit for bit' "$RUNNER_TEMP/selftest.log" + grep -qE '^chains the engine holds: ([0-9]+)/\1 bit for bit' "$RUNNER_TEMP/selftest.log" grep -qE '^[0-9]+ checks, 0 failed$' "$RUNNER_TEMP/selftest.log" - - name: A job on the model, certified and sealed + - name: A job on the model, certified and sealed, with the reference's answer shell: bash working-directory: wai/wai-quantum-landscape-fpga run: | set -euo pipefail printf '{"program": "vars 2\\noppose 0 1\\nsolve\\n", "seed": "7"}' | cargo run --locked --release -- landscape run - --model --demo-key > "$RUNNER_TEMP/job.json" - grep -q '"parity": "bit-exact"' "$RUNNER_TEMP/job.json" grep -q '"id": "fpga:model-landscape-64"' "$RUNNER_TEMP/job.json" + grep -q '"state": "-+"' "$RUNNER_TEMP/job.json" - name: Joint statistics against exact enumeration, on the model shell: bash working-directory: wai/wai-quantum-landscape-fpga diff --git a/wai/changes/2026-11-06-landscape-fpga-member.md b/wai/changes/2026-11-06-landscape-fpga-member.md index 5b4a264cd..914df0d8d 100644 --- a/wai/changes/2026-11-06-landscape-fpga-member.md +++ b/wai/changes/2026-11-06-landscape-fpga-member.md @@ -1,7 +1,7 @@ ### wai-quantum-landscape-fpga: an FPGA board as a member of energy-landscape jobs - **`wai-quantum-landscape-fpga`** is a reference design: the settle and sample kernels of `wai.quantum.landscape/1` in programmable logic for up to 64 cells (`rtl/`), the register-level model the logic is tested against, a testbench that replays the model's session against the logic, and the host that runs jobs on the board as a `Member`, holds it to the conformance set and meters its power by run minus halt. The board, its part and its sensor are named as data in its README. -- **The sample kernel takes the reference's draws**: the logic counts and mixes them as SplitMix64 does and compares 53 bits against cut points the host computes with the reference's `cuts`, so it needs no exponential. On the board both kernels are bit-exact against the `settle` and `chains` vectors, and every worked example over 200 seeds answers as the CPU reference does. +- **The sample kernel takes the reference's draws**: the logic counts and mixes them as SplitMix64 does and compares 53 bits against cut points the host computes with the reference's `cuts`, so it needs no exponential. On the board both kernels were certified bit-exact against the `settle` and `chains` vectors of 2026-11-06, and every worked example over 200 seeds answers as the CPU reference does. The set has since grown past what the engine holds (64 cells, 20-bit sampled fields, a window of 8,192 fields), and a member may not decline those cases, so under the current set the host certifies both kernels `uncertified`; every case the engine holds still gives the reference's bits. - **An AXI link** (`rtl/landscape_axi.v`) replaces the EMIO register port as the default build: the engine's registers on AXI-Lite, bulk loads read from DDR by a write-list DMA, a sequencer that runs a job's chains in fabric, start to result, and a write-back that puts the results in DDR. The link answers every access within a few clocks whatever the engine is doing, and the host reads a signature over EMIO before every AXI access, so a design replaced by another session is caught before an access could go unanswered. A second testbench drives the link and its DMA against the model. The processor-to-logic latency is measured on the board (`latency`). - **The sample kernel is held to its exact joint law** (`joint`, `src/exact.rs`): small workloads enumerated over every state, chains on the board against that law by chi-square and G over all states, every cell's and every pair's frequencies, and a control with the right marginals and independent cells that must be rejected. - **The member is certified by the host** on the conformance set and the statistical battery, runs any schedule of temperatures the job gives, and maps fields and costs beyond its registers exactly for sweeps at 0. diff --git a/wai/wai-quantum-landscape-fpga/README.md b/wai/wai-quantum-landscape-fpga/README.md index 356fa49f5..82b9fc342 100644 --- a/wai/wai-quantum-landscape-fpga/README.md +++ b/wai/wai-quantum-landscape-fpga/README.md @@ -58,8 +58,20 @@ 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 comes back bit for bit (and, for a sampling -kernel that misses, trying the statistical battery). It runs whatever schedule the job gives, one +granting `bit-exact` to a kernel only if every case it takes comes back bit for bit (and, for a +sampling kernel that misses, trying the statistical battery). It declines only what the spec lets a +member decline (§5: sampling fields above 2^31 − 1). + +**What it cannot hold, and what that costs.** The engine has 64 cells, 20-bit field and cost +registers for sampling, and a table window of 8,192 fields a temperature. The conformance set as it +stands now holds cases past all three (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), and +the spec does not let a member decline them. The board refuses each with a sentence, never with +wrong bits, and every case it can hold it gives bit for bit (`conform`). But a refused case is a +missed vector, so the host certifies both kernels `uncertified` under the current set, and a job +on the board says so. The board 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. 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 @@ -192,8 +204,8 @@ every batch's chain starts through the DMA from a memory model that answers with 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, 40,785 lines and 856 checked -reads, and the AXI script, 41,127 lines, 76 write lists, 22 batches and 1,178 checked values, both +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. @@ -240,9 +252,12 @@ in `measurements/2026-11-06/build.txt`. 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`.** 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`. +- **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 the engine can + hold, 21 settle cases and 227 chains, bit for bit, and 13 cases refused as beyond it, so + `uncertified` (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. @@ -370,8 +385,11 @@ with its couplings fixed in the logic. - **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. -- **Sampling is limited to the engine's registers**: fields of ±524,287 and a cost of 1,048,575. - Beyond them it refuses; at temperature 0 they are mapped exactly. +- **Not certified under the current set.** 64 cells, sampled fields of ±524,287 and a cost of + 1,048,575, and a window of 8,192 fields a temperature: the set now holds cases past each, which a + member may not decline, so both kernels come out `uncertified` (above). 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. diff --git a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/build.txt b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/build.txt index 9e63f3a1f..d7a5c06b2 100644 --- a/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/build.txt +++ b/wai/wai-quantum-landscape-fpga/measurements/2026-11-06/build.txt @@ -20,13 +20,13 @@ BUILDS (part xck26-sfvc784-2LV-c, 100 MHz) false-path constraint for the EMIO port's synchroniser that finds no cell in them. TESTBENCHES, on the scripts gen-tb records from this commit's model (sha256 of each script) - EMIO landscape_tb.script bb7278bda25d95515a87e3dea3b4ee914d7de7848f9f1d5f5d1619617f3427f1 - Icarus Verilog 13.0: TB DONE: 40785 lines, 856 checked reads, 0 mismatches, sim time 6087448000 - xsim v2026.1: TB DONE: 40785 lines, 856 checked reads, 0 mismatches, sim time 6087448000 - AXI landscape_axi_tb.script 169fb7ed417a3f7feb7182668d7b6fdc4515fac8156f939c1158ed25916d6fa2 - 76 write lists, 22 batches, 22 write-backs - Icarus Verilog 13.0: TB DONE: 41127 lines, 1178 checked reads, 0 mismatches, sim time 1495786000 - xsim v2026.1: TB DONE: 41127 lines, 1178 checked reads, 0 mismatches, sim time 1495786000 + EMIO landscape_tb.script 4fb49e25f794849db750d688755ef452f05fb9146fd0cb016ad0b533dee5205e + Icarus Verilog 13.0: TB DONE: 174345 lines, 2668 checked reads, 0 mismatches, sim time 26571197000 + xsim v2026.1: TB DONE: 174345 lines, 2668 checked reads, 0 mismatches, sim time 26571197000 + AXI landscape_axi_tb.script 927b463dd40b9a3535fe77a770656415b58ce2dae33a93bf3299b3ab5252fc5c + 171 write lists, 50 batches, 50 write-backs + Icarus Verilog 13.0: TB DONE: 175830 lines, 4464 checked reads, 0 mismatches, sim time 6878426000 + xsim v2026.1: TB DONE: 175830 lines, 4464 checked reads, 0 mismatches, sim time 6878426000 A flipped expected word in a write-back's results (xsim, the earlier script): MISMATCH line 1231: written back chain 0 word 2 got 00000000 want f0000000 The RTL the scripts ran against is the final AXI build's: no file under rtl/ that it is built diff --git a/wai/wai-quantum-landscape-fpga/src/conform.rs b/wai/wai-quantum-landscape-fpga/src/conform.rs index 6cf41357e..2eff51609 100644 --- a/wai/wai-quantum-landscape-fpga/src/conform.rs +++ b/wai/wai-quantum-landscape-fpga/src/conform.rs @@ -10,12 +10,12 @@ //! and on the CPU reference, answer for answer. use crate::engine::Loaded; -use crate::member::{BoardMember, VECTORS, lowered, plan, run_schedule}; +use crate::member::{BoardMember, VECTORS, beyond, lowered, plan, run_schedule}; use crate::port::Port; use crate::regs::*; use serde_json::{Value, json}; use std::fmt::Write as _; -use wai_quantum::quantum_landscape::conformance::{chain_cases, run_battery, settle_cases}; +use wai_quantum::quantum_landscape::conformance::{MUST_TAKE_FIELD_BOUND, battery, chain_cases, run_battery, settle_cases}; use wai_quantum::quantum_landscape::energy::Landscape; use wai_quantum::quantum_landscape::member::{Certified, Chain, Member, check_chain, check_settle, named}; use wai_quantum::quantum_landscape::program::read; @@ -47,8 +47,14 @@ impl Report { } pub struct Bits { + /// Every settle case the engine can hold gave the reference's bits, and every one it cannot + /// failed with a sentence. pub settle_ok: bool, pub chains_ok: bool, + /// Cases beyond the engine's capacity, by name (`member::beyond`): the spec does not let a + /// member decline them, so each costs the kernel its certificate. + pub settle_beyond: Vec, + pub chains_beyond: Vec, pub text: String, } @@ -60,45 +66,94 @@ fn vectors() -> Value { /// (`check_settle`, `check_chain`): what the host's certification grades. pub fn bits(m: &BoardMember

) -> Result { let mut text = String::new(); - let (mut s_ok, mut s_n) = (0, 0); + let may_decline = |w: &Workload| w.field_bound() > MUST_TAKE_FIELD_BOUND; + let (mut s_ok, mut s_n, mut s_declined, mut s_wrong) = (0, 0, 0, 0); + let mut settle_beyond = Vec::new(); for c in settle_cases() { - s_n += 1; let w = c.landscape.lower(); + if m.takes(&w, false).is_err() && may_decline(&w) { + s_declined += 1; + continue; + } let want = w.settle(&c.start, c.bound); - match m.settle(&w, &c.start, c.bound) { + let got = m.settle(&w, &c.start, c.bound); + if let Some(why) = beyond(&w, &[]) { + match got { + Err(_) => settle_beyond.push(format!("{} ({why})", c.name)), + Ok(_) => { + s_wrong += 1; + let _ = writeln!(text, " settle {}: beyond the engine ({why}), and it answered anyway", c.name); + } + } + continue; + } + s_n += 1; + match got { Ok(o) if o == want && check_settle(&o, &w, c.bound).is_ok() => s_ok += 1, Ok(o) => { + s_wrong += 1; let _ = writeln!(text, " settle {}: got {:?}, want {:?}", c.name, o, want); } Err(e) => { + s_wrong += 1; let _ = writeln!(text, " settle {}: {e}", c.name); } } } - let (mut c_ok, mut c_n) = (0, 0); + let (mut c_ok, mut c_n, mut c_declined, mut c_wrong) = (0, 0, 0, 0); + let mut chains_beyond = Vec::new(); for c in chain_cases() { let w = c.landscape.lower(); - let temps = schedule(c.t0, c.sweeps); + let temps = c.temperatures.each(); let chains = c.chains(); + if m.takes(&w, temps.iter().any(|&t| t > 0.0)).is_err() && may_decline(&w) { + c_declined += chains.len(); + continue; + } + let got = m.chains(&w, &chains, &temps); + if let Some(why) = beyond(&w, &temps) { + match got { + Err(_) => chains_beyond.push(format!("{} ({why})", c.name)), + Ok(_) => { + c_wrong += 1; + let _ = writeln!(text, " chains {}: beyond the engine ({why}), and it answered anyway", c.name); + } + } + continue; + } c_n += chains.len(); let want: Vec<_> = chains.iter().map(|ch| anneal_with(&w, &ch.start, &temps, ch.seed)).collect(); - match m.chains(&w, &chains, &temps) { + match got { Ok(got) => { for (g, wnt) in got.iter().zip(&want) { if g == wnt && check_chain(g, &w, &temps).is_ok() { c_ok += 1; } else { + c_wrong += 1; let _ = writeln!(text, " chain {}: got {:?}, want {:?}", c.name, g.state, wnt.state); } } } Err(e) => { + c_wrong += 1; let _ = writeln!(text, " chains {}: {e}", c.name); } } } - let _ = writeln!(text, "settle cases {s_ok}/{s_n} bit for bit; chains {c_ok}/{c_n} bit for bit"); - Ok(Bits { settle_ok: s_ok == s_n, chains_ok: c_ok == c_n, text }) + let _ = writeln!( + text, + "settle cases the engine holds: {s_ok}/{s_n} bit for bit; {} beyond its capacity; {s_declined} declined, as the spec lets a member", + settle_beyond.len() + ); + let _ = writeln!( + text, + "chains the engine holds: {c_ok}/{c_n} bit for bit; {} cases beyond its capacity; {c_declined} chains declined, as the spec lets a member", + chains_beyond.len() + ); + for b in settle_beyond.iter().chain(&chains_beyond) { + let _ = writeln!(text, " beyond the engine: {b}"); + } + Ok(Bits { settle_ok: s_wrong == 0 && s_ok == s_n, chains_ok: c_wrong == 0 && c_ok == c_n, settle_beyond, chains_beyond, text }) } /// `count` chains of one cell, one sweep each at temperature `t`, chain `r` with the seed @@ -125,8 +180,8 @@ pub fn full(m: &BoardMember

, scale: f64) -> Result { let b = bits(m)?; rep.line("wai.quantum.landscape/1 member tests (quantum-conformance/landscape/vectors.json)"); rep.text.push_str(&b.text); - rep.check(b.settle_ok, "settle kernel: the settle cases, bit for bit (state, sweeps, ending, accumulates), each held to the job"); - rep.check(b.chains_ok, "sample kernel: the chain vectors, bit for bit (the state from the device; sweeps, ending, accumulates and draws by the schedule, as check_chain fixes them), each held to the job"); + rep.check(b.settle_ok, "settle kernel: every settle case the engine holds, bit for bit (state, sweeps, ending, accumulates), each held to the job; every one it cannot hold refused"); + rep.check(b.chains_ok, "sample kernel: every chain vector the engine holds, bit for bit (the state from the device; sweeps, ending, accumulates and draws by the schedule, as check_chain fixes them), each held to the job; every one it cannot hold refused"); // the cut points the host gives the engine, against the set's let cells = f["cells"].as_array().unwrap(); @@ -171,13 +226,22 @@ pub fn full(m: &BoardMember

, scale: f64) -> Result { } // the statistical battery, and the all-at-once control it is built to catch - for r in run_battery(m) { - rep.check(r.passed, format!("battery {}: chi-square {:.1} on {} degrees of freedom (critical {:.1})", r.name, r.chi_square, r.degrees, r.critical)); + let held: Vec = battery().iter().map(|c| beyond(&c.landscape.lower(), &c.temperatures).is_none()).collect(); + for (r, holds) in run_battery(m).into_iter().zip(&held) { + if !holds && !r.declined { + let c = battery().into_iter().find(|c| c.name == r.name).expect("the battery's own case"); + let why = beyond(&c.landscape.lower(), &c.temperatures).unwrap_or_default(); + rep.check(!r.passed, format!("battery {}: beyond the engine ({why}), refused", r.name)); + } else if r.declined { + rep.check(r.passed, format!("battery {}: declined, as the spec lets a member", r.name)); + } else { + rep.check(r.passed, format!("battery {}: chi-square {:.1} on {} degrees of freedom (critical {:.1})", r.name, r.chi_square, r.degrees, r.critical)); + } } m.jacobi_control.set(true); let control = run_battery(m); m.jacobi_control.set(false); - let caught: Vec<&str> = control.iter().filter(|r| !r.passed).map(|r| r.name).collect(); + let caught: Vec<&str> = control.iter().zip(&held).filter(|(r, h)| **h && !r.passed).map(|(r, _)| r.name).collect(); rep.check(!caught.is_empty(), format!("the all-at-once control (every cell moved together) fails the battery on {caught:?}")); let (mut ok, mut n) = (0, 0); diff --git a/wai/wai-quantum-landscape-fpga/src/member.rs b/wai/wai-quantum-landscape-fpga/src/member.rs index f100b1634..c0ea2f027 100644 --- a/wai/wai-quantum-landscape-fpga/src/member.rs +++ b/wai/wai-quantum-landscape-fpga/src/member.rs @@ -36,6 +36,7 @@ use crate::engine::{Engine, Loaded}; use crate::port::Port; use crate::regs::*; use std::cell::RefCell; +use wai_quantum::quantum_landscape::conformance::MUST_TAKE_FIELD_BOUND; use wai_quantum::quantum_landscape::member::{Chain, KERNEL_SAMPLE, KERNEL_SETTLE, Member, Parity, certify}; use wai_quantum::quantum_landscape::sample::cuts; use wai_quantum::quantum_landscape::settle::{Outcome, Term, Workload, threshold}; @@ -141,6 +142,38 @@ pub fn lowered_settle(w: &Workload) -> Loaded { l } +/// Why the engine cannot hold a workload under a schedule, if it cannot: more variables than its 64 +/// cells, or, for sweeps above 0, a field or cost beyond its registers or uncertain fields wider +/// than its table's window. Sweeps at 0 it holds at any field ([`lowered_settle`]). The member +/// fails such a workload with a sentence, never with wrong bits; the checks hold it to that. +pub fn beyond(w: &Workload, temperatures: &[f64]) -> Option { + if w.n() > CELLS { + return Some(format!("{} variables, and the engine has {CELLS} cells", w.n())); + } + if temperatures.iter().all(|&t| t <= 0.0) { + return None; + } + let l = lowered(w); + if let Some(f) = l.fields.iter().find(|f| f.abs() > FIELD_MAX) { + return Some(format!("a field of {f} to sample, and the engine's field registers hold ±{FIELD_MAX}")); + } + if l.cost > COST_MAX { + return Some(format!("a cost of {} to sample, and the engine's cost register holds {COST_MAX}", l.cost)); + } + let mut k = 0; + while k < temperatures.len() { + let hot = temperatures[k] > 0.0; + let m = temperatures[k..].iter().take_while(|&&t| (t > 0.0) == hot).count(); + if hot { + if let Err(e) = plan(&l, &temperatures[k..k + m]) { + return Some(e); + } + } + k += m; + } + None +} + /// The fields cell `i` can reach: its own field plus or minus its number of couplings. fn reach(l: &Loaded) -> Vec { let mut hs: Vec = Vec::new(); @@ -275,6 +308,21 @@ impl Member for BoardMember

{ ) } + /// Declines only what `extensions/quantum-landscape.md` §5 lets a member decline: sampling a + /// workload whose field bound is above 2^31 − 1. (Sweeps at 0 it takes at any field: the host + /// maps them exactly, `lowered_settle`.) Everything else it takes, and what the engine cannot + /// hold, more than 64 variables or sampling fields beyond its 20-bit registers, it then fails + /// with a sentence: the spec does not let a member decline those. + fn takes(&self, w: &Workload, sampling: bool) -> Result<(), String> { + if sampling && w.field_bound() > MUST_TAKE_FIELD_BOUND { + return Err(format!( + "its fields reach {}, past 2^31 - 1, and the engine samples fields of at most ±{FIELD_MAX}", + w.field_bound() + )); + } + Ok(()) + } + fn settle(&self, w: &Workload, start: &[i8], bound: u32) -> Result { let l = lowered_settle(w); self.engine.borrow_mut().load(&l).map_err(|e| e.0)?; -- 2.54.0 (Apple Git-157)