logicaffeine_compile/codegen_sva/
reconfig_fabric_rtl.rs1use super::fabric_grid::{CellKind, Chan, Drive, Grid, Src2};
20use super::fabric_rtl::AluOp;
21
22pub struct ReconfigConfig {
26 pub ctrl: Vec<u32>,
27 pub sac: Vec<i64>,
28 pub sbc: Vec<i64>,
29 pub out_sel: usize,
30 pub out_chan: u8,
31}
32
33fn alu_code(op: AluOp) -> u32 {
34 match op {
35 AluOp::Add => 0,
36 AluOp::Sub => 1,
37 AluOp::Mul => 2,
38 AluOp::And => 3,
39 AluOp::Or => 4,
40 AluOp::Xor => 5,
41 AluOp::Shl => 6,
42 AluOp::Shr => 7,
43 AluOp::UShr => 8,
44 }
45}
46
47fn enc_src2(s: Src2) -> (u32, u32, i64) {
49 match s {
50 Src2::Zero => (0, 0, 0),
51 Src2::N => (1, 0, 0),
52 Src2::W => (2, 0, 0),
53 Src2::In(k) => (3, k as u32, 0),
54 Src2::Const(c) => (4, 0, c),
55 }
56}
57
58fn enc_drive(d: Drive) -> u32 {
62 match d {
63 Drive::Alu => 3,
64 Drive::Pass(Src2::Zero) => 0,
65 Drive::Pass(Src2::N) => 1,
66 Drive::Pass(Src2::W) => 2,
67 Drive::Pass(other) => panic!(
68 "reconfig fabric: channel drive Pass({other:?}) is outside the correct-first scope \
69 (Arith-body grids drive channels with Zero/N/W/Alu only)"
70 ),
71 }
72}
73
74pub fn serialize_grid_config(g: &Grid) -> ReconfigConfig {
77 let rc = g.rows * g.cols;
78 let mut ctrl = vec![0u32; rc];
79 let mut sac = vec![0i64; rc];
80 let mut sbc = vec![0i64; rc];
81 for r in 0..g.rows {
82 for c in 0..g.cols {
83 let idx = r * g.cols + c;
84 let t = g.tiles[r][c];
85 let (op, sam, sai, sac_v, sbm, sbi, sbc_v) = match t.alu {
86 None => (0u32, 0u32, 0u32, 0i64, 0u32, 0u32, 0i64),
87 Some(CellKind::Arith(op, a, b)) => {
88 let (am, ai, ac) = enc_src2(a);
89 let (bm, bi, bc) = enc_src2(b);
90 (alu_code(op), am, ai, ac, bm, bi, bc)
91 }
92 Some(other) => panic!(
93 "reconfig fabric: tile {other:?} is outside the Stage-4 correct-first scope (Arith grids)"
94 ),
95 };
96 let vs = enc_drive(t.vs);
97 let he = enc_drive(t.he);
98 ctrl[idx] =
99 op | (sam << 4) | (sai << 7) | (sbm << 11) | (sbi << 14) | (vs << 18) | (he << 20);
100 sac[idx] = sac_v;
101 sbc[idx] = sbc_v;
102 }
103 }
104 let (or, oc, och) = g.out;
105 ReconfigConfig {
106 ctrl,
107 sac,
108 sbc,
109 out_sel: or * g.cols + oc,
110 out_chan: if matches!(och, Chan::He) { 1 } else { 0 },
111 }
112}
113
114fn in_mux(field: &str, n_in: usize) -> String {
116 if n_in == 0 {
117 return "64'sd0".to_string();
118 }
119 let mut acc = "64'sd0".to_string();
120 for k in (0..n_in).rev() {
121 acc = format!("({field}==4'd{k}) ? in{k} : ({acc})");
122 }
123 acc
124}
125
126pub fn emit_reconfig_fabric_verilog(name: &str, rows: usize, cols: usize, n_in: usize) -> String {
132 let rc = rows * cols;
133 let in_ports: String = (0..n_in)
134 .map(|k| format!("input signed [63:0] in{k},\n "))
135 .collect();
136
137 let mut body = String::new();
138 for r in 0..rows {
139 for c in 0..cols {
140 let idx = r * cols + c;
141 let nn = if r == 0 {
142 "64'sd0".to_string()
143 } else {
144 format!("vs_{}_{c}", r - 1)
145 };
146 let ww = if c == 0 {
147 "64'sd0".to_string()
148 } else {
149 format!("he_{r}_{}", c - 1)
150 };
151 body.push_str(&format!(" wire [31:0] ct_{r}_{c} = ctrl[{idx}];\n"));
152 let ain = in_mux(&format!("ct_{r}_{c}[10:7]"), n_in);
153 let bin = in_mux(&format!("ct_{r}_{c}[17:14]"), n_in);
154 body.push_str(&format!(
155 " wire signed [63:0] opa_{r}_{c} = (ct_{r}_{c}[6:4]==3'd0) ? 64'sd0 : (ct_{r}_{c}[6:4]==3'd1) ? {nn} : (ct_{r}_{c}[6:4]==3'd2) ? {ww} : (ct_{r}_{c}[6:4]==3'd3) ? ({ain}) : sac[{idx}];\n"
156 ));
157 body.push_str(&format!(
158 " wire signed [63:0] opb_{r}_{c} = (ct_{r}_{c}[13:11]==3'd0) ? 64'sd0 : (ct_{r}_{c}[13:11]==3'd1) ? {nn} : (ct_{r}_{c}[13:11]==3'd2) ? {ww} : (ct_{r}_{c}[13:11]==3'd3) ? ({bin}) : sbc[{idx}];\n"
159 ));
160 body.push_str(&format!(
161 " wire signed [63:0] al_{r}_{c} = alu_f(ct_{r}_{c}[3:0], opa_{r}_{c}, opb_{r}_{c});\n"
162 ));
163 body.push_str(&format!(
164 " wire signed [63:0] vs_{r}_{c} = drive_f(ct_{r}_{c}[19:18], {nn}, {ww}, al_{r}_{c});\n"
165 ));
166 body.push_str(&format!(
167 " wire signed [63:0] he_{r}_{c} = drive_f(ct_{r}_{c}[21:20], {nn}, {ww}, al_{r}_{c});\n"
168 ));
169 }
170 }
171
172 let mut vs_sel = "64'sd0".to_string();
174 let mut he_sel = "64'sd0".to_string();
175 for r in (0..rows).rev() {
176 for c in (0..cols).rev() {
177 let idx = r * cols + c;
178 vs_sel = format!(
179 "(out_sel=={rc_w}'d{idx}) ? vs_{r}_{c} : ({vs_sel})",
180 rc_w = 16
181 );
182 he_sel = format!(
183 "(out_sel=={rc_w}'d{idx}) ? he_{r}_{c} : ({he_sel})",
184 rc_w = 16
185 );
186 }
187 }
188
189 format!(
190 "// reconfig_{name} — a UNIVERSAL config-driven CGRA ({rows}x{cols}). Every tile's operation and\n\
191 // channel routing are RUNTIME config (loaded via $readmemh); loading route_loop_body's certified\n\
192 // placement (Stage 3) runs ANY bounded Arith block on ONE physical array. Feed-forward → combinational.\n\
193 module reconfig_{name}(\n {in_ports}output signed [63:0] result\n);\n\
194 \x20 reg [31:0] ctrl [0:{rcm1}];\n\
195 \x20 reg signed [63:0] sac [0:{rcm1}];\n\
196 \x20 reg signed [63:0] sbc [0:{rcm1}];\n\
197 \x20 reg [63:0] ohdr [0:1];\n\
198 \x20 reg [15:0] out_sel; reg out_chan;\n\
199 \x20 initial begin\n\
200 \x20 $readmemh(\"reconfig_{name}_ctrl.hex\", ctrl);\n\
201 \x20 $readmemh(\"reconfig_{name}_sac.hex\", sac);\n\
202 \x20 $readmemh(\"reconfig_{name}_sbc.hex\", sbc);\n\
203 \x20 $readmemh(\"reconfig_{name}_out.hex\", ohdr);\n\
204 \x20 out_sel = ohdr[0][15:0]; out_chan = ohdr[1][0];\n\
205 \x20 end\n\
206 \x20 function signed [63:0] alu_f(input [3:0] op, input signed [63:0] a, input signed [63:0] b);\n\
207 \x20 case (op)\n\
208 \x20 4'd0: alu_f = a + b; 4'd1: alu_f = a - b; 4'd2: alu_f = a * b;\n\
209 \x20 4'd3: alu_f = a & b; 4'd4: alu_f = a | b; 4'd5: alu_f = a ^ b;\n\
210 \x20 4'd6: alu_f = a <<< b; 4'd7: alu_f = a >>> b; 4'd8: alu_f = $unsigned(a) >> b; default: alu_f = 64'sd0;\n\
211 \x20 endcase\n\
212 \x20 endfunction\n\
213 \x20 function signed [63:0] drive_f(input [1:0] m, input signed [63:0] nn, input signed [63:0] ww, input signed [63:0] al);\n\
214 \x20 case (m) 2'd0: drive_f = 64'sd0; 2'd1: drive_f = nn; 2'd2: drive_f = ww; 2'd3: drive_f = al; default: drive_f = 64'sd0; endcase\n\
215 \x20 endfunction\n\
216 {body} assign result = out_chan ? ({he_sel}) : ({vs_sel});\n\
217 endmodule\n",
218 rcm1 = rc - 1,
219 )
220}
221
222pub fn emit_reconfig_testbench(name: &str, n_in: usize, n_vectors: usize) -> String {
227 let decls: String = (0..n_in)
228 .map(|k| format!(" reg signed [63:0] in{k};\n"))
229 .collect();
230 let ports: String = {
231 let mut v: Vec<String> = (0..n_in).map(|k| format!(".in{k}(in{k})")).collect();
232 v.push(".result(result)".to_string());
233 v.join(", ")
234 };
235 let pin = if n_in > 0 {
236 format!(" reg signed [63:0] pin [0:{}];\n", n_in * n_vectors - 1)
237 } else {
238 String::new()
239 };
240 let readpin = if n_in > 0 {
241 format!(" $readmemh(\"reconfig_{name}_pin.hex\", pin);\n")
242 } else {
243 String::new()
244 };
245 let drive: String = (0..n_in)
246 .map(|k| format!(" in{k} = pin[i*{n_in}+{k}];\n"))
247 .collect();
248
249 format!(
250 r#"// tb_reconfig_{name} — the reconfigurable fabric (config = a certified placement) vs the RUNNING VM.
251`timescale 1ns/1ps
252module tb_reconfig_{name};
253{decls}{pin} reg signed [63:0] eout [0:{olast}];
254 wire signed [63:0] result;
255 integer i, errors;
256 reconfig_{name} dut({ports});
257 initial begin
258 errors = 0;
259{readpin} $readmemh("reconfig_{name}_eout.hex", eout);
260 for (i = 0; i < {n_vectors}; i = i + 1) begin
261{drive} #1;
262 if (result !== eout[i]) begin errors = errors + 1; $display("FAIL case %0d: got %0d exp %0d", i, result, eout[i]); end
263 end
264 if (errors == 0) $display("PASS: {n_vectors} reconfigurable-fabric evaluations match the VM");
265 else $display("FAIL: %0d reconfig mismatches", errors);
266 $finish;
267 end
268endmodule
269"#,
270 olast = n_vectors - 1,
271 )
272}