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