Skip to main content

logicaffeine_compile/codegen_sva/
fabric_soc.rs

1//! fabric_soc — Phase E: the FLASHABLE reconfigurable-fabric SoC.
2//!
3//! Phase D proved (our SAT) that a diverse program corpus projects onto ONE reconfigurable tile array under a
4//! config word. This module makes that array real hardware: a synthesizable `fabric_soc` wraps the
5//! `config_<name>` array ([`emit_config_grid_verilog`]) with registered I/O and a CYCLE COUNTER, so a host
6//! presents `(cfg, inputs)` and reads back `(result, cycles)`. Because the array is feed-forward
7//! (combinational), a decision settles in ONE clock — the honest cycles/op the interpreter pays hundreds of
8//! fetch-decode-execute cycles for. The self-checking testbench drives the SoC over a vector battery and
9//! `$display`s `(result, cycles)`; `fabric_soc_lock` runs it under iverilog and checks every result against
10//! the VM and every cycle count against the 1-clock settle. The RTL, its testbench, and the constraints are
11//! drift-locked (regenerate with `FABRIC_SOC_RECORD=1`), and the config-grid ≡ VM is the eval side of
12//! `fabric_whole_program_lock` (Phase D).
13
14use super::fabric_config::{compile_to_config_table, ConfigTable};
15use super::fabric_grid::emit_config_grid_verilog;
16use super::fabric_rtl::{
17    decision_fabric_from_source, emit_float_datapath_verilog, emit_state_plane_verilog,
18    STATE_PLANE_HEAP_WORDS,
19};
20
21/// The fabric SoC's program corpus — the datapaths the ONE reconfigurable array can become, selected by the
22/// config word. Single-final-`Return` + conditional-`Set` style so branches if-convert to select tiles.
23pub const FABRIC_SOURCES: &[(&str, &str)] = &[
24    ("## To affine (x: Int) -> Int:\n    Return 3 * x + 7.\n", "affine"),
25    ("## To clamp (x: Int, lo: Int, hi: Int) -> Int:\n    Let result be x.\n    If x is less than lo:\n        Set result to lo.\n    If x is greater than hi:\n        Set result to hi.\n    Return result.\n", "clamp"),
26    ("## To sign (n: Int) -> Int:\n    Let result be 0.\n    If n is less than 0:\n        Set result to 0 - 1.\n    If n is greater than 0:\n        Set result to 1.\n    Return result.\n", "sign"),
27    ("## To min2 (a: Int, b: Int) -> Int:\n    Let result be b.\n    If a is less than b:\n        Set result to a.\n    Return result.\n", "min2"),
28    // Stage X1 (XPU): a DIVISION datapath — a Func (functional-unit) tile on the fabric, not just Arith/Select.
29    ("## To ratio (a: Int, b: Int) -> Int:\n    Return a / b.\n", "ratio"),
30];
31
32/// The datapath / config-word widths (8-bit datapath for the SAT proof; 3-bit config indexes ≤ 8 datapaths).
33pub const WIDTH: u32 = 8;
34pub const CFG_WIDTH: u32 = 3;
35/// The SoC's input-port count — the max arity over the corpus (`clamp` has three). Functions with fewer
36/// inputs ignore the extra ports (their tiles never reference them).
37pub const N_IN: usize = 3;
38
39/// The compiled config table (padded islands on one array). The single source of truth for the RTL and the
40/// testbench's expected values — both derive from it, never hand-typed.
41pub fn table() -> ConfigTable {
42    compile_to_config_table(FABRIC_SOURCES, WIDTH, CFG_WIDTH)
43}
44
45/// The reconfigurable array RTL (`config_fabric`): ONE tile grid becoming any corpus datapath under `cfg`.
46pub fn emit_config_grid() -> String {
47    let t = table();
48    let refs: Vec<&super::fabric_grid::Grid> = t.grids.iter().collect();
49    emit_config_grid_verilog("fabric", &refs, CFG_WIDTH)
50}
51
52/// The flashable SoC: registered `(cfg, inputs)` in, the combinational `config_fabric`, a `cycles` counter,
53/// and registered `(result, cycles, done)` out. On `rst` release the array's output settles in one clock; the
54/// SoC latches it, sets `cycles = 1` (the honest cost of a spatial decision), and raises `done`. Synthesizable
55/// as-is — the inputs wire to switches / a UART loader and the outputs to LEDs / a UART readback for bring-up.
56pub fn emit_fabric_soc() -> String {
57    let ins = (0..N_IN)
58        .map(|k| format!("  input signed [63:0] in{k},\n"))
59        .collect::<String>();
60    let conns = (0..N_IN)
61        .map(|k| format!(".in{k}(in{k}), "))
62        .collect::<String>();
63    format!(
64        "// fabric_soc — a flashable wrapper around config_fabric (the reconfigurable CGRA of Phase D). Present\n\
65         // `cfg` + inputs, pulse `rst`, and after ONE settle clock `result`/`cycles` are valid and `done` is\n\
66         // high. `cycles == 1`: a spatial decision settles in one clock, the metric the interpreter pays\n\
67         // hundreds of fetch-decode-execute cycles for. Proven ≡ the VM under iverilog by `fabric_soc_lock`.\n\
68         module fabric_soc (\n\
69         \x20 input        clk,\n\
70         \x20 input        rst,          // active-high synchronous reset / start\n\
71         \x20 input  [{cw}:0] cfg,          // the config word: which datapath the array becomes\n\
72         {ins}\
73         \x20 output reg signed [63:0] result,\n\
74         \x20 output reg [15:0] cycles,   // clocks from start to a valid result (the honest cycles/op)\n\
75         \x20 output reg        done\n\
76         );\n\
77         \x20 wire signed [63:0] fab_out;\n\
78         \x20 config_fabric u_fab({conns}.cfg(cfg), .result(fab_out));\n\
79         \x20 reg running;\n\
80         \x20 always @(posedge clk) begin\n\
81         \x20\x20 if (rst) begin\n\
82         \x20\x20\x20 result <= 64'sd0; cycles <= 16'd0; done <= 1'b0; running <= 1'b1;\n\
83         \x20\x20 end else if (running) begin\n\
84         \x20\x20\x20 // the combinational array has settled; latch it. One clock = one decision.\n\
85         \x20\x20\x20 result <= fab_out; cycles <= 16'd1; done <= 1'b1; running <= 1'b0;\n\
86         \x20\x20 end\n\
87         \x20 end\n\
88         endmodule\n",
89        cw = CFG_WIDTH - 1,
90    )
91}
92
93/// The self-checking testbench: drive the SoC over `(cfg, inputs)` vectors and `$display` `cfg result cycles`
94/// per vector. `fabric_soc_lock` parses these and checks each result ≡ the VM and each cycle count == 1.
95pub fn emit_fabric_soc_tb() -> String {
96    let ins = (0..N_IN)
97        .map(|k| format!("  reg signed [63:0] in{k};\n"))
98        .collect::<String>();
99    let conns = (0..N_IN)
100        .map(|k| format!(".in{k}(in{k}), "))
101        .collect::<String>();
102    let set0 = (0..N_IN).map(|k| format!("in{k}=0; ")).collect::<String>();
103    // One task drives a vector: reset, present cfg+inputs, clock until done, print.
104    format!(
105        "`timescale 1ns/1ps\n\
106         module tb_fabric_soc;\n\
107         \x20 reg clk = 0, rst = 0;\n\
108         \x20 reg [{cw}:0] cfg;\n\
109         {ins}\
110         \x20 wire signed [63:0] result;\n\
111         \x20 wire [15:0] cycles;\n\
112         \x20 wire done;\n\
113         \x20 fabric_soc dut(.clk(clk), .rst(rst), .cfg(cfg), {conns}.result(result), .cycles(cycles), .done(done));\n\
114         \x20 always #5 clk = ~clk;\n\
115         \x20 task run(input [{cw}:0] c, input signed [63:0] a0, input signed [63:0] a1, input signed [63:0] a2);\n\
116         \x20 begin\n\
117         \x20\x20 @(negedge clk); cfg=c; {set0} in0=a0; in1=a1; in2=a2; rst=1;\n\
118         \x20\x20 @(negedge clk); rst=0;\n\
119         \x20\x20 wait (done);\n\
120         \x20\x20 @(negedge clk);\n\
121         \x20\x20 $display(\"%0d %0d %0d\", c, $signed(result), cycles);\n\
122         \x20 end\n\
123         \x20 endtask\n\
124         \x20 initial begin\n\
125         {vectors}\
126         \x20\x20 $finish;\n\
127         \x20 end\n\
128         endmodule\n",
129        cw = CFG_WIDTH - 1,
130        vectors = tb_vectors(),
131    )
132}
133
134/// The test vectors the testbench drives — a battery per datapath, spanning each function's cases. Kept in
135/// sync with `fabric_soc_lock`'s expected computation (both iterate this same list).
136pub fn vectors() -> Vec<(u64, [i64; 3])> {
137    vec![
138        (0, [0, 0, 0]),
139        (0, [3, 0, 0]),
140        (0, [-5, 0, 0]),
141        (1, [5, 0, 10]),
142        (1, [-3, 0, 10]),
143        (1, [20, 0, 10]),
144        (2, [-7, 0, 0]),
145        (2, [0, 0, 0]),
146        (2, [9, 0, 0]),
147        (3, [3, 8, 0]),
148        (3, [8, 3, 0]),
149        // cfg 4 = ratio (a / b, truncating toward zero) — the DIVISION Func tile. Non-zero divisors.
150        (4, [10, 3, 0]),
151        (4, [100, 7, 0]),
152        (4, [-20, 4, 0]),
153    ]
154}
155
156fn tb_vectors() -> String {
157    vectors()
158        .iter()
159        .map(|(c, a)| {
160            format!(
161                "  run({cw}'d{c}, {}, {}, {});\n",
162                a[0],
163                a[1],
164                a[2],
165                cw = CFG_WIDTH
166            )
167        })
168        .collect()
169}
170
171/// Emit the BOARD-FLASHABLE top (`nexys_fabric_soc`) for the Nexys A7-100T: the reconfigurable `config_fabric`
172/// array, a baked-in vector ROM, and a settle sweep that measures on the chip how many clocks each datapath
173/// takes. For every vector the sweep loads a prime into the launch register, lets it settle, launches the vector
174/// and captures the array's output `L` clocks later, for `L` from `LMAX - 1` down to 1, under two primes (the
175/// vector's inputs inverted, and the next vector), `REPS` times. The settled value is the capture at `2·LMAX`; a
176/// result is settled inside the window only when the capture at `LMAX` already equals it under both primes. The
177/// streamed cycle count is the smallest `L` from which every capture equals the settled value, the worst over the
178/// repetitions, and 0 when the result did not settle inside the window. Each record goes out the UART TX as
179/// `(sync, cfg, result[8], cycles[2])`; a host checks each result against the VM. 100 MHz board clock, 115200
180/// baud (CLKS_PER_BIT = 868). Pins in `nexys_fabric_soc.xdc`.
181pub fn emit_nexys_fabric_soc() -> String {
182    let vs = vectors();
183    let nvec = vs.len();
184    let cw = CFG_WIDTH;
185    let cwm = cw - 1;
186    let rw = cw + 191;
187    let pad = 8 - cw;
188    let word = |n: i64| format!("64'h{:016x}", n as u64);
189    let mut rom = String::new();
190    for (i, (c, a)) in vs.iter().enumerate() {
191        rom.push_str(&format!(
192            "      {i}: rom = {{{cw}'d{c}, {}, {}, {}}};\n",
193            word(a[0]),
194            word(a[1]),
195            word(a[2]),
196        ));
197    }
198    format!(
199        "// nexys_fabric_soc — the Phase-D reconfigurable fabric on a Nexys A7-100T, measuring its own settle. A\n\
200         // baked-in vector ROM steps the ONE config_fabric array through every corpus datapath. For each vector a\n\
201         // settle sweep primes the launch register, launches the vector, and captures the array's output L clocks\n\
202         // later, for L from LMAX-1 down to 1, under two primes (the inputs inverted; the next vector), REPS times.\n\
203         // The settled value is the capture at 2*LMAX, and it counts only when the capture at LMAX already equals it.\n\
204         // Each record streams out the UART TX as {{0xA5, cfg, result[63:0] MSB-first, cycles[15:0]}}: cycles is\n\
205         // the smallest L from which every capture equals the settled result, the worst over the repetitions, and\n\
206         // 0 when the result did not settle inside the window. 100 MHz board clock; 8N1 115200 (CLKS_PER_BIT=868).\n\
207         // Host: scripts/fabric-host.py checks each result against the VM.\n\
208         module nexys_fabric_soc #(parameter CLKS_PER_BIT = 868, parameter LMAX = 255, parameter REPS = 8) (\n\
209         \x20 input        clk,\n\
210         \x20 input        rst,\n\
211         \x20 output reg   uart_tx,\n\
212         \x20 output [15:0] led\n\
213         );\n\
214         \x20 localparam NVEC = {nvec};\n\
215         \x20 // vector ROM: {{cfg, in0, in1, in2}} of vector i.\n\
216         \x20 function [{rw}:0] rom(input [5:0] i);\n\
217         \x20\x20 case (i)\n\
218         {rom}\
219         \x20\x20\x20 default: rom = 0;\n\
220         \x20\x20 endcase\n\
221         \x20 endfunction\n\
222         \x20 reg  [5:0] vec;\n\
223         \x20 wire [5:0] nxt_vec = (vec == NVEC - 1) ? 6'd0 : vec + 6'd1;\n\
224         \x20 wire [{rw}:0] tgt = rom(vec);\n\
225         \x20 wire [{rw}:0] nxt = rom(nxt_vec);\n\
226         \x20 // the launch register: the array computes from these.\n\
227         \x20 reg [{cwm}:0] l_cfg; reg [63:0] l_in0, l_in1, l_in2;\n\
228         \x20 wire [63:0] fab_out;\n\
229         \x20 config_fabric u_fab(.in0(l_in0), .in1(l_in1), .in2(l_in2), .cfg(l_cfg), .result(fab_out));\n\
230         \x20 localparam M_PRIME=4'd0, M_SETTLE=4'd1, M_LAUNCH=4'd2, M_WAIT=4'd3, M_CHECK=4'd4,\n\
231         \x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20 M_LOAD=4'd5, M_START=4'd6, M_DATA=4'd7, M_STOP=4'd8;\n\
232         \x20 reg [3:0]  m;\n\
233         \x20 reg [15:0] cnt;      // clocks since the launch register was last loaded\n\
234         \x20 reg [15:0] lat;      // the capture latency under test\n\
235         \x20 reg [15:0] settle;   // the worst settle over the repetitions so far\n\
236         \x20 reg [15:0] rep;\n\
237         \x20 reg        scan;     // 0: the reference captures; 1: the descending scan\n\
238         \x20 reg        prime_b;  // 0: the inverted inputs prime the launch; 1: the next vector does\n\
239         \x20 reg        stable;   // every reference agreed: the result settled inside the window\n\
240         \x20 reg        have_ref; // result holds the first repetition's settled value\n\
241         \x20 reg [63:0] cap;      // the capture at LMAX (reference)\n\
242         \x20 reg [63:0] cap2;     // the capture at 2*LMAX (reference) or at lat (scan)\n\
243         \x20 reg [63:0] ref_v;    // this repetition's settled value\n\
244         \x20 reg [63:0] result;   // the settled value the record streams\n\
245         \x20 wire [15:0] want = scan ? lat : 16'd2 * LMAX;\n\
246         \x20 wire [15:0] miss = lat + 16'd1;\n\
247         \x20 wire [15:0] cycles_out = stable ? settle : 16'd0;\n\
248         \x20 wire        last_rep = (rep == REPS - 1);\n\
249         \x20 // UART TX byte sequencer: 12 bytes a record.\n\
250         \x20 reg [15:0] clk_cnt;\n\
251         \x20 reg [2:0]  bit_idx;\n\
252         \x20 reg [7:0]  tx_byte;\n\
253         \x20 reg [3:0]  pos;\n\
254         \x20 assign led = {{m, vec, settle[5:0]}};\n\
255         \x20 wire [7:0] res_byte = result[(9 - pos) * 8 +: 8];\n\
256         \x20 always @(posedge clk) begin\n\
257         \x20\x20 if (rst) begin\n\
258         \x20\x20\x20 m<=M_PRIME; uart_tx<=1'b1; clk_cnt<=16'd0; bit_idx<=3'd0; pos<=4'd0; tx_byte<=8'd0; vec<=6'd0;\n\
259         \x20\x20\x20 cnt<=16'd0; lat<=16'd0; settle<=16'd0; rep<=16'd0; scan<=1'b0; prime_b<=1'b0; stable<=1'b1; have_ref<=1'b0;\n\
260         \x20\x20\x20 l_cfg<={cw}'d0; l_in0<=64'd0; l_in1<=64'd0; l_in2<=64'd0; cap<=64'd0; cap2<=64'd0; ref_v<=64'd0; result<=64'd0;\n\
261         \x20\x20 end else case (m)\n\
262         \x20\x20\x20 M_PRIME: begin\n\
263         \x20\x20\x20\x20 if (!prime_b) begin\n\
264         \x20\x20\x20\x20\x20 l_cfg<=tgt[{rw}:192]; l_in0<=~tgt[191:128]; l_in1<=~tgt[127:64]; l_in2<=~tgt[63:0];\n\
265         \x20\x20\x20\x20 end else begin\n\
266         \x20\x20\x20\x20\x20 l_cfg<=nxt[{rw}:192]; l_in0<=nxt[191:128]; l_in1<=nxt[127:64]; l_in2<=nxt[63:0];\n\
267         \x20\x20\x20\x20 end\n\
268         \x20\x20\x20\x20 cnt<=16'd1; m<=M_SETTLE;\n\
269         \x20\x20\x20 end\n\
270         \x20\x20\x20 M_SETTLE: if (cnt == 16'd2 * LMAX) m<=M_LAUNCH; else cnt<=cnt+16'd1;\n\
271         \x20\x20\x20 M_LAUNCH: begin\n\
272         \x20\x20\x20\x20 l_cfg<=tgt[{rw}:192]; l_in0<=tgt[191:128]; l_in1<=tgt[127:64]; l_in2<=tgt[63:0];\n\
273         \x20\x20\x20\x20 cnt<=16'd1; m<=M_WAIT;\n\
274         \x20\x20\x20 end\n\
275         \x20\x20\x20 M_WAIT: begin\n\
276         \x20\x20\x20\x20 if (!scan && cnt == LMAX) cap<=fab_out;\n\
277         \x20\x20\x20\x20 if (cnt == want) begin cap2<=fab_out; m<=M_CHECK; end else cnt<=cnt+16'd1;\n\
278         \x20\x20\x20 end\n\
279         \x20\x20\x20 M_CHECK: begin\n\
280         \x20\x20\x20\x20 if (!scan) begin\n\
281         \x20\x20\x20\x20\x20 if (cap != cap2) stable<=1'b0;\n\
282         \x20\x20\x20\x20\x20 if (!prime_b) begin ref_v<=cap2; prime_b<=1'b1; m<=M_PRIME; end\n\
283         \x20\x20\x20\x20\x20 else begin\n\
284         \x20\x20\x20\x20\x20\x20 if (cap2 != ref_v) stable<=1'b0;\n\
285         \x20\x20\x20\x20\x20\x20 if (!have_ref) begin result<=ref_v; have_ref<=1'b1; end else if (ref_v != result) stable<=1'b0;\n\
286         \x20\x20\x20\x20\x20\x20 scan<=1'b1; prime_b<=1'b0; lat<=LMAX - 16'd1; m<=M_PRIME;\n\
287         \x20\x20\x20\x20\x20 end\n\
288         \x20\x20\x20\x20 end else if (cap2 != ref_v || (prime_b && lat == 16'd1)) begin\n\
289         \x20\x20\x20\x20\x20 if (cap2 != ref_v) begin if (miss > settle) settle<=miss; end\n\
290         \x20\x20\x20\x20\x20 else if (settle == 16'd0) settle<=16'd1;\n\
291         \x20\x20\x20\x20\x20 if (last_rep) begin pos<=4'd0; m<=M_LOAD; end\n\
292         \x20\x20\x20\x20\x20 else begin rep<=rep+16'd1; scan<=1'b0; prime_b<=1'b0; m<=M_PRIME; end\n\
293         \x20\x20\x20\x20 end else if (!prime_b) begin prime_b<=1'b1; m<=M_PRIME; end\n\
294         \x20\x20\x20\x20 else begin prime_b<=1'b0; lat<=lat-16'd1; m<=M_PRIME; end\n\
295         \x20\x20\x20 end\n\
296         \x20\x20\x20 M_LOAD: begin\n\
297         \x20\x20\x20\x20 case (pos)\n\
298         \x20\x20\x20\x20\x20 4'd0: tx_byte<=8'hA5;\n\
299         \x20\x20\x20\x20\x20 4'd1: tx_byte<={{{pad}'d0, tgt[{rw}:192]}};\n\
300         \x20\x20\x20\x20\x20 4'd10: tx_byte<=cycles_out[15:8];\n\
301         \x20\x20\x20\x20\x20 4'd11: tx_byte<=cycles_out[7:0];\n\
302         \x20\x20\x20\x20\x20 default: tx_byte<=res_byte;\n\
303         \x20\x20\x20\x20 endcase\n\
304         \x20\x20\x20\x20 clk_cnt<=16'd0; bit_idx<=3'd0; m<=M_START;\n\
305         \x20\x20\x20 end\n\
306         \x20\x20\x20 M_START: begin uart_tx<=1'b0; if (clk_cnt==CLKS_PER_BIT-1) begin clk_cnt<=16'd0; m<=M_DATA; end else clk_cnt<=clk_cnt+16'd1; end\n\
307         \x20\x20\x20 M_DATA: begin uart_tx<=tx_byte[bit_idx];\n\
308         \x20\x20\x20\x20 if (clk_cnt==CLKS_PER_BIT-1) begin clk_cnt<=16'd0; if (bit_idx==3'd7) m<=M_STOP; else bit_idx<=bit_idx+3'd1; end else clk_cnt<=clk_cnt+16'd1;\n\
309         \x20\x20\x20 end\n\
310         \x20\x20\x20 M_STOP: begin uart_tx<=1'b1;\n\
311         \x20\x20\x20\x20 if (clk_cnt==CLKS_PER_BIT-1) begin clk_cnt<=16'd0;\n\
312         \x20\x20\x20\x20\x20 if (pos==4'd11) begin\n\
313         \x20\x20\x20\x20\x20\x20 // the next vector, measured afresh; the stream loops so a host can lock onto any lap.\n\
314         \x20\x20\x20\x20\x20\x20 vec<=nxt_vec; rep<=16'd0; settle<=16'd0; stable<=1'b1; have_ref<=1'b0; scan<=1'b0; prime_b<=1'b0; m<=M_PRIME;\n\
315         \x20\x20\x20\x20\x20 end else begin pos<=pos+4'd1; m<=M_LOAD; end\n\
316         \x20\x20\x20\x20 end else clk_cnt<=clk_cnt+16'd1;\n\
317         \x20\x20\x20 end\n\
318         \x20\x20\x20 default: m<=M_PRIME;\n\
319         \x20\x20 endcase\n\
320         \x20 end\n\
321         endmodule\n",
322    )
323}
324
325/// The board pin constraints for `nexys_fabric_soc` (Nexys A7-100T): 100 MHz clock (E3), reset (J15),
326/// UART TX (D4), status LEDs. No `uart_rx` — the vector ROM is baked in, so the SoC only streams out.
327pub fn emit_nexys_fabric_xdc() -> String {
328    let mut s = String::from(
329        "## Nexys A7-100T pin constraints for nexys_fabric_soc (100 MHz, UART TX readback only).\n\
330         set_property -dict { PACKAGE_PIN E3  IOSTANDARD LVCMOS33 } [get_ports { clk }]\n\
331         create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports { clk }]\n\
332         set_property -dict { PACKAGE_PIN J15 IOSTANDARD LVCMOS33 } [get_ports { rst }]\n\
333         set_property -dict { PACKAGE_PIN D4  IOSTANDARD LVCMOS33 } [get_ports { uart_tx }]\n",
334    );
335    const LED_PINS: [&str; 16] = [
336        "H17", "K15", "J13", "N14", "R18", "V17", "U17", "U16", "V16", "T15", "U14", "T16", "V15",
337        "V14", "V12", "V11",
338    ];
339    for (i, pin) in LED_PINS.iter().enumerate() {
340        s.push_str(&format!(
341            "set_property -dict {{ PACKAGE_PIN {pin} IOSTANDARD LVCMOS33 }} [get_ports {{ led[{i}] }}]\n"
342        ));
343    }
344    s
345}
346
347// ══════════════════════════════════════════════════════════════════════════════════════════════════
348// STAGE X2c (XPU): the STATE PLANE on real silicon. Unlike the combinational config_fabric (one array
349// muxed across datapaths, 1 clock/op), a state-plane kernel is a SEQUENCED datapath (`state_<name>`, a
350// LUTRAM heap + a micro-sequencer). So its SoC drives ONE kernel over a vector battery: it pulses the
351// datapath's reset, WAITS for `done` (counting the MULTI-cycle cost — the honest state-plane latency,
352// not 1), captures the result, and streams the 12-byte record. This puts collections/structs on the
353// fabric CHIP, matching X1's silicon bar.
354
355/// The flashed state-plane kernel: `grade(a,b,c)` is a WHOLE MULTI-OP-CLASS program — it builds the list
356/// `[a,b,c]` on the heap (COLLECTIONS), sums its elements (ARITHMETIC), and clamps the total to `[0, 100]`
357/// (CONTROL FLOW — two if-converted branches). So the flashed datapath exercises three distinct op-classes at
358/// once — Mem (HeapAlloc/Write/Read) + Arith (adds) + Select (the clamps) — all in ONE sequenced datapath on
359/// silicon: the fabric running a real program, not a single op.
360pub const STATE_SOC_SRC: &str = "## To grade (a: Int, b: Int, c: Int) -> Int:\n    Let xs be [a, b, c].\n    Let total be item 1 of xs + item 2 of xs + item 3 of xs.\n    Let result be total.\n    If total is greater than 100:\n        Set result to 100.\n    If total is less than 0:\n        Set result to 0.\n    Return result.\n";
361pub const STATE_SOC_NAME: &str = "grade";
362
363/// The input battery the state SoC runs on silicon (each a `(a,b,c)` triple → `clamp(a+b+c, 0, 100)`). Chosen
364/// to exercise the pass-through, the UPPER clamp (sum > 100), and the LOWER clamp (sum < 0) — so the control
365/// flow is genuinely taken on hardware, not just present.
366pub fn state_soc_vectors() -> Vec<[i64; 3]> {
367    vec![
368        [10, 20, 30],    // 60  (pass-through)
369        [1, 2, 3],       // 6   (pass-through)
370        [40, 40, 40],    // 120 -> 100 (upper clamp)
371        [-40, -40, -40], // -120 -> 0 (lower clamp)
372        [33, 33, 33],    // 99  (pass-through, near the ceiling)
373        [0, 0, 0],       // 0
374        [70, 20, 5],     // 95  (pass-through)
375    ]
376}
377
378/// The sequenced state-plane datapath RTL (`state_grade`) — the LUTRAM-heap micro-sequencer for the kernel.
379pub fn emit_state_soc_module() -> String {
380    let df = decision_fabric_from_source(STATE_SOC_SRC, STATE_SOC_NAME);
381    emit_state_plane_verilog(STATE_SOC_NAME, &df, STATE_PLANE_HEAP_WORDS)
382}
383
384/// The SIMPLE state SoC (`state_soc`) for the iverilog proof: it wraps `state_<name>` with a CYCLE COUNTER
385/// (cycles from reset-release to `done`) and exposes `result`/`cycles`/`done`. A host/testbench presents
386/// `(a,b,c)`, pulses `rst`, and reads back the multi-cycle result — the board-independent ≡VM proof surface.
387pub fn emit_state_soc() -> String {
388    format!(
389        "// state_soc — the sequenced state-plane datapath under a cycle counter (Stage X2c iverilog surface).\n\
390         // On `rst` the datapath restarts; the counter runs until `done`, so `cycles` is the HONEST multi-cycle\n\
391         // state-plane latency (a heap program is NOT a 1-clock decision). `result` is valid when `done` rises.\n\
392         module state_soc(\n\
393         \x20 input               clk,\n\
394         \x20 input               rst,\n\
395         \x20 input  signed [63:0] in0,\n\
396         \x20 input  signed [63:0] in1,\n\
397         \x20 input  signed [63:0] in2,\n\
398         \x20 output signed [63:0] result,\n\
399         \x20 output [15:0]       cycles,\n\
400         \x20 output              done\n\
401         );\n\
402         \x20 wire signed [63:0] dp_out;\n\
403         \x20 wire dp_done;\n\
404         \x20 reg [15:0] cyc; reg counting;\n\
405         \x20 state_{name} u_dp(.clk(clk), .rst(rst), .in0(in0), .in1(in1), .in2(in2), .result(dp_out), .done(dp_done));\n\
406         \x20 always @(posedge clk) begin\n\
407         \x20\x20 if (rst) begin cyc <= 16'd0; counting <= 1'b1; end\n\
408         \x20\x20 else if (counting) begin if (dp_done) counting <= 1'b0; else cyc <= cyc + 16'd1; end\n\
409         \x20 end\n\
410         \x20 assign result = dp_out;\n\
411         \x20 assign cycles = cyc;\n\
412         \x20 assign done = dp_done;\n\
413         endmodule\n",
414        name = STATE_SOC_NAME,
415    )
416}
417
418/// A self-checking testbench for `state_soc`: drive each `(a,b,c)` vector, pulse `rst`, run until `done`, and
419/// `$display(\"result cycles\")`. `state_soc_lock` runs it under iverilog and checks each result ≡ the VM and
420/// each cycle count > 1 (a sequenced state program, not a 1-clock combinational decision).
421pub fn emit_state_soc_tb() -> String {
422    let vs = state_soc_vectors();
423    let lit = |n: i64| {
424        if n < 0 {
425            format!("-64'sd{}", -n)
426        } else {
427            format!("64'sd{n}")
428        }
429    };
430    let mut runs = String::new();
431    for a in &vs {
432        runs.push_str(&format!(
433            "    run({}, {}, {});\n",
434            lit(a[0]),
435            lit(a[1]),
436            lit(a[2])
437        ));
438    }
439    format!(
440        "// tb_state_soc — the sequenced state plane vs the RUNNING VM over the list-sum battery.\n\
441         `timescale 1ns/1ps\n\
442         module tb_state_soc;\n\
443         \x20 reg clk = 0, rst;\n\
444         \x20 reg signed [63:0] in0, in1, in2;\n\
445         \x20 wire signed [63:0] result;\n\
446         \x20 wire [15:0] cycles;\n\
447         \x20 wire done;\n\
448         \x20 integer c;\n\
449         \x20 state_soc dut(.clk(clk), .rst(rst), .in0(in0), .in1(in1), .in2(in2), .result(result), .cycles(cycles), .done(done));\n\
450         \x20 always #5 clk = ~clk;\n\
451         \x20 task run(input signed [63:0] a0, input signed [63:0] a1, input signed [63:0] a2);\n\
452         \x20 begin\n\
453         \x20\x20 @(negedge clk); in0 = a0; in1 = a1; in2 = a2; rst = 1;\n\
454         \x20\x20 @(negedge clk); rst = 0;\n\
455         \x20\x20 c = 0; while (!done && c < 200) begin @(negedge clk); c = c + 1; end\n\
456         \x20\x20 $display(\"%0d %0d\", result, cycles);\n\
457         \x20 end\n\
458         \x20 endtask\n\
459         \x20 initial begin\n\
460         {runs}\
461         \x20\x20 $finish;\n\
462         \x20 end\n\
463         endmodule\n",
464    )
465}
466
467/// The FLASHABLE state SoC (`nexys_state_soc`) for the Nexys A7-100T: a baked-in `(a,b,c)` vector ROM drives
468/// `state_<name>` — pulse the datapath reset, wait `done` (counting the multi-cycle latency), capture the
469/// result, and stream the 12-byte record `{0xA5, vec, result[63:0] MSB-first, cycles[15:0]}` out the UART TX,
470/// looping forever so a host can frame-lock at any time. 100 MHz; 8N1 115200 (CLKS_PER_BIT=868).
471pub fn emit_nexys_state_soc() -> String {
472    let vs = state_soc_vectors();
473    let nvec = vs.len();
474    let mut rom = String::new();
475    for (i, a) in vs.iter().enumerate() {
476        let lit = |n: i64| {
477            if n < 0 {
478                format!("-64'sd{}", -n)
479            } else {
480                format!("64'sd{n}")
481            }
482        };
483        rom.push_str(&format!(
484            "      {i}: begin v_in0 = {}; v_in1 = {}; v_in2 = {}; end\n",
485            lit(a[0]),
486            lit(a[1]),
487            lit(a[2]),
488        ));
489    }
490    format!(
491        "// nexys_state_soc — the sequenced STATE PLANE on a Nexys A7-100T (Stage X2c). A baked-in vector ROM\n\
492         // drives state_{name} (a LUTRAM-heap micro-sequencer) over the list-sum battery; each result streams\n\
493         // out the UART TX as a 12-byte record {{0xA5, vec, result[63:0] MSB-first, cycles[15:0]}}. cycles is\n\
494         // the HONEST multi-cycle state-plane latency (not the combinational 1). 100 MHz; 8N1 115200.\n\
495         module nexys_state_soc #(parameter CLKS_PER_BIT = 868) (\n\
496         \x20 input        clk,\n\
497         \x20 input        rst,\n\
498         \x20 output reg   uart_tx,\n\
499         \x20 output [15:0] led\n\
500         );\n\
501         \x20 localparam NVEC = {nvec};\n\
502         \x20 reg [5:0] vec;\n\
503         \x20 reg signed [63:0] v_in0, v_in1, v_in2;\n\
504         \x20 always @(*) begin\n\
505         \x20\x20 v_in0 = 64'sd0; v_in1 = 64'sd0; v_in2 = 64'sd0;\n\
506         \x20\x20 case (vec)\n\
507         {rom}\
508         \x20\x20\x20 default: ;\n\
509         \x20\x20 endcase\n\
510         \x20 end\n\
511         \x20 // the sequenced datapath + a run controller.\n\
512         \x20 reg dp_rst; wire dp_done; wire signed [63:0] dp_out;\n\
513         \x20 state_{name} u_dp(.clk(clk), .rst(dp_rst), .in0(v_in0), .in1(v_in1), .in2(v_in2), .result(dp_out), .done(dp_done));\n\
514         \x20 reg signed [63:0] cap_out; reg [15:0] cap_cyc;\n\
515         \x20 localparam S_RESET=4'd0, S_DRST=4'd1, S_DWAIT=4'd2, S_CAP=4'd3, S_LOAD=4'd4, S_START=4'd5, S_DATA=4'd6, S_STOP=4'd7, S_DONE=4'd8;\n\
516         \x20 reg [3:0]  state;\n\
517         \x20 reg [15:0] clk_cnt;\n\
518         \x20 reg [2:0]  bit_idx;\n\
519         \x20 reg [7:0]  tx_byte;\n\
520         \x20 reg [3:0]  pos;\n\
521         \x20 reg [3:0]  rst_cnt;\n\
522         \x20 reg [3:0]  dprst_cnt;\n\
523         \x20 assign led = {{state, vec[4:0], 7'd0}};\n\
524         \x20 wire [7:0] res_byte = cap_out[(9 - pos) * 8 +: 8];\n\
525         \x20 always @(posedge clk) begin\n\
526         \x20\x20 if (rst) begin\n\
527         \x20\x20\x20 state<=S_RESET; uart_tx<=1'b1; clk_cnt<=16'd0; bit_idx<=3'd0; vec<=6'd0; pos<=4'd0; rst_cnt<=4'd0;\n\
528         \x20\x20\x20 tx_byte<=8'd0; dp_rst<=1'b1; dprst_cnt<=4'd0; cap_out<=64'sd0; cap_cyc<=16'd0;\n\
529         \x20\x20 end else case (state)\n\
530         \x20\x20\x20 S_RESET: if (rst_cnt==4'd8) begin state<=S_DRST; dp_rst<=1'b1; dprst_cnt<=4'd0; end else rst_cnt<=rst_cnt+4'd1;\n\
531         \x20\x20\x20 // pulse the datapath reset so it latches inputs + zeroes its heap, then run.\n\
532         \x20\x20\x20 S_DRST: begin dp_rst<=1'b1; if (dprst_cnt==4'd3) begin state<=S_DWAIT; dp_rst<=1'b0; cap_cyc<=16'd0; end else dprst_cnt<=dprst_cnt+4'd1; end\n\
533         \x20\x20\x20 S_DWAIT: begin dp_rst<=1'b0; if (dp_done) begin cap_out<=dp_out; state<=S_CAP; end else cap_cyc<=cap_cyc+16'd1; end\n\
534         \x20\x20\x20 S_CAP: begin pos<=4'd0; state<=S_LOAD; end\n\
535         \x20\x20\x20 S_LOAD: begin\n\
536         \x20\x20\x20\x20 case (pos)\n\
537         \x20\x20\x20\x20\x20 4'd0: tx_byte<=8'hA5;\n\
538         \x20\x20\x20\x20\x20 4'd1: tx_byte<={{2'd0, vec}};\n\
539         \x20\x20\x20\x20\x20 4'd10: tx_byte<=cap_cyc[15:8];\n\
540         \x20\x20\x20\x20\x20 4'd11: tx_byte<=cap_cyc[7:0];\n\
541         \x20\x20\x20\x20\x20 default: tx_byte<=res_byte;\n\
542         \x20\x20\x20\x20 endcase\n\
543         \x20\x20\x20\x20 clk_cnt<=16'd0; bit_idx<=3'd0; state<=S_START;\n\
544         \x20\x20\x20 end\n\
545         \x20\x20\x20 S_START: begin uart_tx<=1'b0; if (clk_cnt==CLKS_PER_BIT-1) begin clk_cnt<=16'd0; state<=S_DATA; end else clk_cnt<=clk_cnt+16'd1; end\n\
546         \x20\x20\x20 S_DATA: begin uart_tx<=tx_byte[bit_idx];\n\
547         \x20\x20\x20\x20 if (clk_cnt==CLKS_PER_BIT-1) begin clk_cnt<=16'd0; if (bit_idx==3'd7) state<=S_STOP; else bit_idx<=bit_idx+3'd1; end else clk_cnt<=clk_cnt+16'd1;\n\
548         \x20\x20\x20 end\n\
549         \x20\x20\x20 S_STOP: begin uart_tx<=1'b1;\n\
550         \x20\x20\x20\x20 if (clk_cnt==CLKS_PER_BIT-1) begin clk_cnt<=16'd0;\n\
551         \x20\x20\x20\x20\x20 if (pos==4'd11) begin\n\
552         \x20\x20\x20\x20\x20\x20 pos<=4'd0;\n\
553         \x20\x20\x20\x20\x20\x20 if (vec==NVEC-1) vec<=6'd0; else vec<=vec+6'd1;\n\
554         \x20\x20\x20\x20\x20\x20 state<=S_DRST; dp_rst<=1'b1; dprst_cnt<=4'd0;\n\
555         \x20\x20\x20\x20\x20 end else begin pos<=pos+4'd1; state<=S_LOAD; end\n\
556         \x20\x20\x20\x20 end else clk_cnt<=clk_cnt+16'd1;\n\
557         \x20\x20\x20 end\n\
558         \x20\x20\x20 S_DONE: uart_tx<=1'b1;\n\
559         \x20\x20\x20 default: state<=S_DONE;\n\
560         \x20\x20 endcase\n\
561         \x20 end\n\
562         endmodule\n",
563        name = STATE_SOC_NAME,
564    )
565}
566
567/// The Nexys A7-100T pin constraints for `nexys_state_soc` — identical ports to the fabric SoC (100 MHz clk,
568/// reset, UART TX, LEDs), so the same pin map.
569pub fn emit_nexys_state_xdc() -> String {
570    emit_nexys_fabric_xdc().replace("nexys_fabric_soc", "nexys_state_soc")
571}
572
573// ══════════════════════════════════════════════════════════════════════════════════════════════════
574// STAGE X3b (XPU): the FLOAT LANE on real silicon. A float kernel is a SEQUENCED datapath binding fpu.v
575// (multi-cycle IEEE-754 units). This SoC drives ONE float kernel over a battery of float inputs: it pulses
576// the datapath reset, WAITS for `done` (counting the FPU pipeline cycles), captures the f64 RESULT BITS, and
577// streams the 12-byte record. The host decodes the f64 and floors it to the integer hypotenuse. This puts
578// IEEE-754 floating point on the fabric CHIP, matching X1/X2c's silicon bar.
579
580/// The flashed float kernel: `fhypf(a,b) = sqrt(a*a + b*b)` — the Euclidean hypotenuse as a Real. Exercises the
581/// full float lane: `fp_mul` (twice) + `fp_addsub` + `fp_sqrt`. It returns a FLOAT (not floored) so the SoC
582/// streams the raw f64 bits — the host floors to the integer hypotenuse (no float→int RTL needed on-chip).
583pub const FLOAT_SOC_SRC: &str =
584    "## To fhypf (a: Float, b: Float) -> Real:\n    Return sqrt(a * a + b * b).\n";
585pub const FLOAT_SOC_NAME: &str = "fhypf";
586
587/// The `(a, b)` float pairs the float SoC runs on silicon → each hypotenuse (mostly Pythagorean integers).
588pub fn float_soc_vectors() -> Vec<(f64, f64)> {
589    vec![
590        (3.0, 4.0),  // 5
591        (6.0, 8.0),  // 10
592        (5.0, 12.0), // 13
593        (8.0, 15.0), // 17
594        (0.0, 7.0),  // 7
595        (2.5, 6.0),  // 6.5 -> floor 6
596        (9.0, 12.0), // 15
597    ]
598}
599
600/// The sequenced float datapath RTL (`float_fhypf`) — the fpu.v-bound micro-sequencer for the kernel.
601pub fn emit_float_soc_module() -> String {
602    let df = decision_fabric_from_source(FLOAT_SOC_SRC, FLOAT_SOC_NAME);
603    emit_float_datapath_verilog(FLOAT_SOC_NAME, &df)
604}
605
606/// The SIMPLE float SoC (`float_soc`) for the iverilog proof: `float_<name>` under a CYCLE COUNTER, exposing
607/// the f64 `result` bits / `cycles` / `done`. A testbench presents float bit-patterns and reads the result.
608pub fn emit_float_soc() -> String {
609    format!(
610        "// float_soc — the sequenced float datapath (binding fpu.v) under a cycle counter (X3b iverilog surface).\n\
611         // `cycles` is the FPU pipeline latency (fp_mul/fp_addsub/fp_sqrt in sequence); `result` is the f64\n\
612         // bit-pattern, valid when `done` rises.\n\
613         module float_soc(\n\
614         \x20 input               clk,\n\
615         \x20 input               rst,\n\
616         \x20 input  [63:0]       in0,\n\
617         \x20 input  [63:0]       in1,\n\
618         \x20 output [63:0]       result,\n\
619         \x20 output [15:0]       cycles,\n\
620         \x20 output              done\n\
621         );\n\
622         \x20 wire [63:0] dp_out; wire dp_done;\n\
623         \x20 reg [15:0] cyc; reg counting;\n\
624         \x20 float_{name} u_dp(.clk(clk), .rst(rst), .in0(in0), .in1(in1), .result(dp_out), .done(dp_done));\n\
625         \x20 always @(posedge clk) begin\n\
626         \x20\x20 if (rst) begin cyc <= 16'd0; counting <= 1'b1; end\n\
627         \x20\x20 else if (counting) begin if (dp_done) counting <= 1'b0; else cyc <= cyc + 16'd1; end\n\
628         \x20 end\n\
629         \x20 assign result = dp_out;\n\
630         \x20 assign cycles = cyc;\n\
631         \x20 assign done = dp_done;\n\
632         endmodule\n",
633        name = FLOAT_SOC_NAME,
634    )
635}
636
637/// A self-checking testbench for `float_soc`: drive each float pair (bit-patterns), run to `done`, and
638/// `$display(\"result_hex cycles\")`. `float_soc_lock` runs it under iverilog (with fpu.v) and checks each
639/// result's f64 bits ≡ the model/Rust and each cycle count > 1.
640pub fn emit_float_soc_tb() -> String {
641    let vs = float_soc_vectors();
642    let mut runs = String::new();
643    for (a, b) in &vs {
644        runs.push_str(&format!(
645            "    run(64'h{:016x}, 64'h{:016x});\n",
646            a.to_bits(),
647            b.to_bits()
648        ));
649    }
650    format!(
651        "// tb_float_soc — the sequenced float lane (fpu.v) vs the f64 reference over the hypotenuse battery.\n\
652         `timescale 1ns/1ps\n\
653         module tb_float_soc;\n\
654         \x20 reg clk = 0, rst;\n\
655         \x20 reg [63:0] in0, in1;\n\
656         \x20 wire [63:0] result;\n\
657         \x20 wire [15:0] cycles;\n\
658         \x20 wire done;\n\
659         \x20 integer c;\n\
660         \x20 float_soc dut(.clk(clk), .rst(rst), .in0(in0), .in1(in1), .result(result), .cycles(cycles), .done(done));\n\
661         \x20 always #5 clk = ~clk;\n\
662         \x20 task run(input [63:0] a, input [63:0] b);\n\
663         \x20 begin\n\
664         \x20\x20 @(negedge clk); in0 = a; in1 = b; rst = 1;\n\
665         \x20\x20 @(negedge clk); rst = 0;\n\
666         \x20\x20 c = 0; while (!done && c < 5000) begin @(negedge clk); c = c + 1; end\n\
667         \x20\x20 $display(\"%h %0d\", result, cycles);\n\
668         \x20 end\n\
669         \x20 endtask\n\
670         \x20 initial begin\n\
671         {runs}\
672         \x20\x20 $finish;\n\
673         \x20 end\n\
674         endmodule\n",
675    )
676}
677
678/// The FLASHABLE float SoC (`nexys_float_soc`) for the Nexys A7-100T: a baked-in `(a,b)` float ROM drives
679/// `float_<name>` — pulse the datapath reset, wait `done` (counting the FPU pipeline cycles), capture the f64
680/// result, and stream the 12-byte record `{0xA5, vec, result[63:0] MSB-first, cycles[15:0]}` out the UART TX,
681/// looping forever. The host decodes the f64 and floors it to the integer hypotenuse. 100 MHz; 8N1 115200.
682pub fn emit_nexys_float_soc() -> String {
683    let vs = float_soc_vectors();
684    let nvec = vs.len();
685    let mut rom = String::new();
686    for (i, (a, b)) in vs.iter().enumerate() {
687        rom.push_str(&format!(
688            "      {i}: begin v_in0 = 64'h{:016x}; v_in1 = 64'h{:016x}; end\n",
689            a.to_bits(),
690            b.to_bits(),
691        ));
692    }
693    format!(
694        "// nexys_float_soc — the sequenced FLOAT LANE on a Nexys A7-100T (Stage X3b). A baked-in float ROM\n\
695         // drives float_{name} (an fpu.v-bound micro-sequencer) over the hypotenuse battery; each f64 result\n\
696         // streams out the UART TX as a 12-byte record {{0xA5, vec, result[63:0] MSB-first, cycles[15:0]}}.\n\
697         // cycles = the FPU pipeline latency (fp_mul/fp_addsub/fp_sqrt in sequence).\n\
698         // The multi-cycle FPU has deep combinational shifter stages (nextpnr Fmax ~13 MHz), so the 100 MHz\n\
699         // board clock is DIVIDED by 16 to a ~6.25 MHz core clock the FPU meets timing on — running it at the\n\
700         // full 100 MHz latches wrong intermediates (proven on silicon). 8N1 115200 at 6.25 MHz (CLKS_PER_BIT=54).\n\
701         module nexys_float_soc #(parameter CLKS_PER_BIT = 54) (\n\
702         \x20 input        clk,\n\
703         \x20 input        rst,\n\
704         \x20 output reg   uart_tx,\n\
705         \x20 output [15:0] led\n\
706         );\n\
707         \x20 // Divide the 100 MHz board clock to ~6.25 MHz so the multi-cycle FPU meets timing on real silicon.\n\
708         \x20 reg [3:0] clkdiv = 4'd0;\n\
709         \x20 always @(posedge clk) clkdiv <= clkdiv + 4'd1;\n\
710         \x20 wire coreclk = clkdiv[3];\n\
711         \x20 localparam NVEC = {nvec};\n\
712         \x20 reg [5:0] vec;\n\
713         \x20 reg [63:0] v_in0, v_in1;\n\
714         \x20 always @(*) begin\n\
715         \x20\x20 v_in0 = 64'd0; v_in1 = 64'd0;\n\
716         \x20\x20 case (vec)\n\
717         {rom}\
718         \x20\x20\x20 default: ;\n\
719         \x20\x20 endcase\n\
720         \x20 end\n\
721         \x20 reg dp_rst; wire dp_done; wire [63:0] dp_out;\n\
722         \x20 float_{name} u_dp(.clk(coreclk), .rst(dp_rst), .in0(v_in0), .in1(v_in1), .result(dp_out), .done(dp_done));\n\
723         \x20 reg [63:0] cap_out; reg [15:0] cap_cyc;\n\
724         \x20 localparam S_RESET=4'd0, S_DRST=4'd1, S_DWAIT=4'd2, S_CAP=4'd3, S_LOAD=4'd4, S_START=4'd5, S_DATA=4'd6, S_STOP=4'd7, S_DONE=4'd8;\n\
725         \x20 reg [3:0]  state;\n\
726         \x20 reg [15:0] clk_cnt;\n\
727         \x20 reg [2:0]  bit_idx;\n\
728         \x20 reg [7:0]  tx_byte;\n\
729         \x20 reg [3:0]  pos;\n\
730         \x20 reg [3:0]  rst_cnt;\n\
731         \x20 reg [3:0]  dprst_cnt;\n\
732         \x20 assign led = {{state, vec[4:0], 7'd0}};\n\
733         \x20 wire [7:0] res_byte = cap_out[(9 - pos) * 8 +: 8];\n\
734         \x20 always @(posedge coreclk) begin\n\
735         \x20\x20 if (rst) begin\n\
736         \x20\x20\x20 state<=S_RESET; uart_tx<=1'b1; clk_cnt<=16'd0; bit_idx<=3'd0; vec<=6'd0; pos<=4'd0; rst_cnt<=4'd0;\n\
737         \x20\x20\x20 tx_byte<=8'd0; dp_rst<=1'b1; dprst_cnt<=4'd0; cap_out<=64'd0; cap_cyc<=16'd0;\n\
738         \x20\x20 end else case (state)\n\
739         \x20\x20\x20 S_RESET: if (rst_cnt==4'd8) begin state<=S_DRST; dp_rst<=1'b1; dprst_cnt<=4'd0; end else rst_cnt<=rst_cnt+4'd1;\n\
740         \x20\x20\x20 S_DRST: begin dp_rst<=1'b1; if (dprst_cnt==4'd3) begin state<=S_DWAIT; dp_rst<=1'b0; cap_cyc<=16'd0; end else dprst_cnt<=dprst_cnt+4'd1; end\n\
741         \x20\x20\x20 S_DWAIT: begin dp_rst<=1'b0; if (dp_done) begin cap_out<=dp_out; state<=S_CAP; end else cap_cyc<=cap_cyc+16'd1; end\n\
742         \x20\x20\x20 S_CAP: begin pos<=4'd0; state<=S_LOAD; end\n\
743         \x20\x20\x20 S_LOAD: begin\n\
744         \x20\x20\x20\x20 case (pos)\n\
745         \x20\x20\x20\x20\x20 4'd0: tx_byte<=8'hA5;\n\
746         \x20\x20\x20\x20\x20 4'd1: tx_byte<={{2'd0, vec}};\n\
747         \x20\x20\x20\x20\x20 4'd10: tx_byte<=cap_cyc[15:8];\n\
748         \x20\x20\x20\x20\x20 4'd11: tx_byte<=cap_cyc[7:0];\n\
749         \x20\x20\x20\x20\x20 default: tx_byte<=res_byte;\n\
750         \x20\x20\x20\x20 endcase\n\
751         \x20\x20\x20\x20 clk_cnt<=16'd0; bit_idx<=3'd0; state<=S_START;\n\
752         \x20\x20\x20 end\n\
753         \x20\x20\x20 S_START: begin uart_tx<=1'b0; if (clk_cnt==CLKS_PER_BIT-1) begin clk_cnt<=16'd0; state<=S_DATA; end else clk_cnt<=clk_cnt+16'd1; end\n\
754         \x20\x20\x20 S_DATA: begin uart_tx<=tx_byte[bit_idx];\n\
755         \x20\x20\x20\x20 if (clk_cnt==CLKS_PER_BIT-1) begin clk_cnt<=16'd0; if (bit_idx==3'd7) state<=S_STOP; else bit_idx<=bit_idx+3'd1; end else clk_cnt<=clk_cnt+16'd1;\n\
756         \x20\x20\x20 end\n\
757         \x20\x20\x20 S_STOP: begin uart_tx<=1'b1;\n\
758         \x20\x20\x20\x20 if (clk_cnt==CLKS_PER_BIT-1) begin clk_cnt<=16'd0;\n\
759         \x20\x20\x20\x20\x20 if (pos==4'd11) begin\n\
760         \x20\x20\x20\x20\x20\x20 pos<=4'd0;\n\
761         \x20\x20\x20\x20\x20\x20 if (vec==NVEC-1) vec<=6'd0; else vec<=vec+6'd1;\n\
762         \x20\x20\x20\x20\x20\x20 state<=S_DRST; dp_rst<=1'b1; dprst_cnt<=4'd0;\n\
763         \x20\x20\x20\x20\x20 end else begin pos<=pos+4'd1; state<=S_LOAD; end\n\
764         \x20\x20\x20\x20 end else clk_cnt<=clk_cnt+16'd1;\n\
765         \x20\x20\x20 end\n\
766         \x20\x20\x20 S_DONE: uart_tx<=1'b1;\n\
767         \x20\x20\x20 default: state<=S_DONE;\n\
768         \x20\x20 endcase\n\
769         \x20 end\n\
770         endmodule\n",
771        name = FLOAT_SOC_NAME,
772    )
773}
774
775/// The Nexys A7-100T pin constraints for `nexys_float_soc` — same ports as the fabric SoC, same pin map.
776pub fn emit_nexys_float_xdc() -> String {
777    emit_nexys_fabric_xdc().replace("nexys_fabric_soc", "nexys_float_soc")
778}
779
780#[cfg(test)]
781#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/fabric_soc/tests.rs"]
782mod tests;
783
784// ── the placed-and-routed arrays as data, for the technical drawings ─────────────────────────────────────
785
786fn drawing_src2(s: super::fabric_grid::Src2) -> String {
787    use super::fabric_grid::Src2;
788    match s {
789        Src2::N => "N".into(),
790        Src2::W => "W".into(),
791        Src2::In(k) => format!("In{k}"),
792        Src2::Const(c) => format!("C{c}"),
793        Src2::Zero => "Zero".into(),
794    }
795}
796
797fn drawing_drive(d: super::fabric_grid::Drive) -> String {
798    use super::fabric_grid::Drive;
799    match d {
800        Drive::Alu => "Alu".into(),
801        Drive::Pass(s) => drawing_src2(s),
802    }
803}
804
805fn drawing_cell(k: super::fabric_grid::CellKind) -> serde_json::Value {
806    use super::fabric_grid::CellKind;
807    use serde_json::json;
808    match k {
809        CellKind::Arith(op, a, b) => {
810            json!({ "kind": "Arith", "op": format!("{op:?}"), "a": drawing_src2(a), "b": drawing_src2(b) })
811        }
812        CellKind::Select(cmp, ca, cb, t, e) => json!({
813            "kind": "Select", "cmp": format!("{cmp:?}"), "ca": drawing_src2(ca), "cb": drawing_src2(cb),
814            "t": drawing_src2(t), "e": drawing_src2(e)
815        }),
816        CellKind::Func(u, a, b) => {
817            json!({ "kind": "Func", "op": format!("{u:?}"), "a": drawing_src2(a), "b": drawing_src2(b) })
818        }
819        CellKind::Simd(op, lane, a, b) => json!({
820            "kind": "Simd", "op": format!("{op:?}"), "lane": format!("{lane:?}"), "a": drawing_src2(a), "b": drawing_src2(b)
821        }),
822        CellKind::Mem(op, a, b) => {
823            json!({ "kind": "Mem", "op": format!("{op:?}"), "a": drawing_src2(a), "b": drawing_src2(b) })
824        }
825    }
826}
827
828fn drawing_out(o: (usize, usize, super::fabric_grid::Chan)) -> serde_json::Value {
829    serde_json::json!([
830        o.0,
831        o.1,
832        if o.2 == super::fabric_grid::Chan::Vs {
833            "vs"
834        } else {
835            "he"
836        }
837    ])
838}
839
840/// One placed grid as JSON: its shape, the tap that carries the result, and every tile's compute + the source
841/// each of its two channels passes (`N`/`W` neighbour, `In<k>` input, `C<v>` constant, `Zero`, or `Alu`).
842fn drawing_grid(name: &str, source: &str, g: &super::fabric_grid::Grid) -> serde_json::Value {
843    let (rows, cols, used) = super::fabric_grid::grid_area(g);
844    serde_json::json!({
845        "name": name,
846        "source": source,
847        "rows": rows,
848        "cols": cols,
849        "n_in": g.n_in,
850        "used_tiles": used,
851        "used_channels": g.used_channels(),
852        "out": drawing_out(g.out),
853        "extra_outs": g.extra_outs.iter().map(|o| drawing_out(*o)).collect::<Vec<_>>(),
854        "tiles": g.tiles.iter().map(|row| row.iter().map(|t| serde_json::json!({
855            "alu": t.alu.map(drawing_cell),
856            "vs": drawing_drive(t.vs),
857            "he": drawing_drive(t.he),
858        })).collect::<Vec<_>>()).collect::<Vec<_>>(),
859    })
860}
861
862/// The placed-and-routed tile arrays as JSON — the source of truth for every technical drawing of the fabric,
863/// so a drawing of a tile array is the placer's own output, never hand-drawn.
864/// Contains the five `config_fabric` islands (the 8×3 array that runs 14/14 vectors on both boards) and the
865/// Horner cubic residual on both placers (the diagonal `route_straight_line` behind `grid_horner.v`, and the
866/// area-minimizing `route_leveled`). Pinned to `benchmarks/silicon/fabric_grids.json` by `fabric_grids_lock`.
867pub fn fabric_grids_json() -> serde_json::Value {
868    use super::fabric_grid::{route_leveled, route_straight_line};
869    use super::fabric_rtl::{poly_program, residual_from_source};
870    use serde_json::json;
871    let t = table();
872    let islands: Vec<serde_json::Value> = t
873        .grids
874        .iter()
875        .zip(FABRIC_SOURCES)
876        .enumerate()
877        .map(|(cfg, (g, (src, name)))| {
878            let mut v = drawing_grid(name, src, g);
879            v["cfg"] = json!(cfg);
880            v
881        })
882        .collect();
883    let poly_src = poly_program("Show poly(4).");
884    let (ops, result, n_in) = residual_from_source(&poly_src, "poly");
885    let cells: Vec<serde_json::Value> = ops
886        .iter()
887        .enumerate()
888        .map(|(i, c)| json!({ "i": i, "op": format!("{:?}", c.op), "a": format!("{:?}", c.a), "b": format!("{:?}", c.b) }))
889        .collect();
890    json!({
891        "config_fabric": {
892            "rows": t.rows, "cols": t.cols, "cfg_width": t.cfg_width, "width": t.width,
893            "islands": islands,
894        },
895        "horner": {
896            "source": poly_src,
897            "cells": cells,
898            "result": result,
899            "n_in": n_in,
900            "diagonal": drawing_grid("horner_diagonal", &poly_src, &route_straight_line(&ops, n_in, result)),
901            "leveled": drawing_grid("horner_leveled", &poly_src, &route_leveled(&ops, n_in, result)),
902        },
903    })
904}