open-standards/wai/wai-quantum-landscape-fpga/rtl/landscape_core.v
Transaction Science c1137390e2 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 <[email protected]>
2026-11-06 03:05:19 -05:00

457 lines
20 KiB
Verilog

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