1use 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
21pub 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 ("## To ratio (a: Int, b: Int) -> Int:\n Return a / b.\n", "ratio"),
30];
31
32pub const WIDTH: u32 = 8;
34pub const CFG_WIDTH: u32 = 3;
35pub const N_IN: usize = 3;
38
39pub fn table() -> ConfigTable {
42 compile_to_config_table(FABRIC_SOURCES, WIDTH, CFG_WIDTH)
43}
44
45pub 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
52pub 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
93pub 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 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
134pub 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 (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
171pub 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
325pub 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
347pub 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
363pub fn state_soc_vectors() -> Vec<[i64; 3]> {
367 vec![
368 [10, 20, 30], [1, 2, 3], [40, 40, 40], [-40, -40, -40], [33, 33, 33], [0, 0, 0], [70, 20, 5], ]
376}
377
378pub 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
384pub 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
418pub 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
467pub 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
567pub fn emit_nexys_state_xdc() -> String {
570 emit_nexys_fabric_xdc().replace("nexys_fabric_soc", "nexys_state_soc")
571}
572
573pub 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
587pub fn float_soc_vectors() -> Vec<(f64, f64)> {
589 vec![
590 (3.0, 4.0), (6.0, 8.0), (5.0, 12.0), (8.0, 15.0), (0.0, 7.0), (2.5, 6.0), (9.0, 12.0), ]
598}
599
600pub 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
606pub 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
637pub 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
678pub 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
775pub 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
784fn 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
840fn 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
862pub 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}