1use super::fabric_rtl::{
14 emit_state_plane_verilog, eval_decision_fabric_heap, DecisionFabric, LoopCell, Src,
15};
16use super::fabric_target::FabricTarget;
17
18#[derive(Clone, Debug)]
22pub struct DaeSchedule {
23 pub n_in: usize,
24 pub layout: Vec<LoopCell>,
25 pub result: Src,
26 pub agu_len: usize,
28 pub is_agu: Vec<bool>,
30}
31
32impl DaeSchedule {
33 pub fn agu_is_pure(&self) -> bool {
36 self.layout
37 .iter()
38 .zip(&self.is_agu)
39 .filter(|(_, a)| **a)
40 .all(|(c, _)| !is_heap_cell(c))
41 }
42
43 pub fn exec_lane_depth(&self) -> usize {
46 let mut depth = vec![0usize; self.layout.len()];
47 let mut ans = 0usize;
48 for i in 0..self.layout.len() {
49 if self.is_agu[i] {
50 continue; }
52 let d = 1 + self.operand_exec_depth(&self.layout[i], &depth);
53 depth[i] = d;
54 ans = ans.max(d);
55 }
56 ans
57 }
58
59 pub fn agu_lane_depth(&self) -> usize {
61 let mut depth = vec![0usize; self.layout.len()];
62 let mut ans = 0usize;
63 for i in 0..self.layout.len() {
64 if !self.is_agu[i] {
65 continue;
66 }
67 let d = 1 + cell_operands(&self.layout[i])
68 .iter()
69 .map(|s| match s {
70 Src::Val(k) => depth[*k],
71 _ => 0,
72 })
73 .max()
74 .unwrap_or(0);
75 depth[i] = d;
76 ans = ans.max(d);
77 }
78 ans
79 }
80
81 pub fn decoupled_critical_depth(&self) -> usize {
84 self.agu_lane_depth().max(self.exec_lane_depth())
85 }
86
87 fn operand_exec_depth(&self, cell: &LoopCell, depth: &[usize]) -> usize {
88 cell_operands(cell)
89 .iter()
90 .map(|s| match s {
91 Src::Val(k) if self.is_agu[*k] => 0,
93 Src::Val(k) => depth[*k],
94 _ => 0,
95 })
96 .max()
97 .unwrap_or(0)
98 }
99}
100
101pub fn is_heap_cell(cell: &LoopCell) -> bool {
104 matches!(
105 cell,
106 LoopCell::HeapRead { .. }
107 | LoopCell::HeapWrite { .. }
108 | LoopCell::HeapAlloc { .. }
109 | LoopCell::MemRead { .. }
110 )
111}
112
113fn cell_operands(cell: &LoopCell) -> Vec<Src> {
115 match cell {
116 LoopCell::Arith { a, b, .. }
117 | LoopCell::Func { a, b, .. }
118 | LoopCell::VectorArith { a, b, .. } => vec![*a, *b],
119 LoopCell::Select { ca, cb, t, e, .. } => vec![*ca, *cb, *t, *e],
120 LoopCell::HeapRead { addr } | LoopCell::MemRead { addr } => vec![*addr],
121 LoopCell::HeapWrite { addr, val } => vec![*addr, *val],
122 LoopCell::HeapAlloc { size } => vec![*size],
123 }
124}
125
126fn address_operands(cell: &LoopCell) -> Vec<Src> {
129 match cell {
130 LoopCell::HeapRead { addr } | LoopCell::MemRead { addr } => vec![*addr],
131 LoopCell::HeapWrite { addr, .. } => vec![*addr],
132 LoopCell::HeapAlloc { size } => vec![*size],
133 _ => vec![],
134 }
135}
136
137pub fn coupled_critical_depth(cells: &[LoopCell], result: Src) -> usize {
140 let mut depth = vec![0usize; cells.len()];
141 for i in 0..cells.len() {
142 depth[i] = 1 + cell_operands(&cells[i])
143 .iter()
144 .map(|s| match s {
145 Src::Val(k) => depth[*k],
146 _ => 0,
147 })
148 .max()
149 .unwrap_or(0);
150 }
151 match result {
152 Src::Val(k) => depth[k],
153 _ => depth.iter().copied().max().unwrap_or(0),
154 }
155}
156
157fn remap(src: Src, newpos: &[usize]) -> Src {
159 match src {
160 Src::Val(k) => Src::Val(newpos[k]),
161 other => other,
162 }
163}
164
165fn remap_cell(cell: &LoopCell, newpos: &[usize]) -> LoopCell {
167 match *cell {
168 LoopCell::Arith { op, a, b } => LoopCell::Arith {
169 op,
170 a: remap(a, newpos),
171 b: remap(b, newpos),
172 },
173 LoopCell::Func { unit, a, b } => LoopCell::Func {
174 unit,
175 a: remap(a, newpos),
176 b: remap(b, newpos),
177 },
178 LoopCell::VectorArith { op, ty, a, b } => LoopCell::VectorArith {
179 op,
180 ty,
181 a: remap(a, newpos),
182 b: remap(b, newpos),
183 },
184 LoopCell::Select { cmp, ca, cb, t, e } => LoopCell::Select {
185 cmp,
186 ca: remap(ca, newpos),
187 cb: remap(cb, newpos),
188 t: remap(t, newpos),
189 e: remap(e, newpos),
190 },
191 LoopCell::HeapRead { addr } => LoopCell::HeapRead {
192 addr: remap(addr, newpos),
193 },
194 LoopCell::MemRead { addr } => LoopCell::MemRead {
195 addr: remap(addr, newpos),
196 },
197 LoopCell::HeapWrite { addr, val } => LoopCell::HeapWrite {
198 addr: remap(addr, newpos),
199 val: remap(val, newpos),
200 },
201 LoopCell::HeapAlloc { size } => LoopCell::HeapAlloc {
202 size: remap(size, newpos),
203 },
204 }
205}
206
207pub fn decouple_access(cells: &[LoopCell], result: Src, n_in: usize) -> DaeSchedule {
214 let n = cells.len();
215
216 let mut taint = vec![false; n];
218 for i in 0..n {
219 taint[i] = is_heap_cell(&cells[i])
220 || cell_operands(&cells[i])
221 .iter()
222 .any(|s| matches!(s, Src::Val(k) if taint[*k]));
223 }
224
225 let mut feeds_addr = vec![false; n];
228 for cell in cells {
229 for s in address_operands(cell) {
230 if let Src::Val(k) = s {
231 feeds_addr[k] = true;
232 }
233 }
234 }
235 for i in (0..n).rev() {
236 if feeds_addr[i] {
237 for s in cell_operands(&cells[i]) {
238 if let Src::Val(k) = s {
239 feeds_addr[k] = true;
240 }
241 }
242 }
243 }
244
245 let is_agu_orig: Vec<bool> = (0..n).map(|i| feeds_addr[i] && !taint[i]).collect();
247
248 let order: Vec<usize> = (0..n)
250 .filter(|&i| is_agu_orig[i])
251 .chain((0..n).filter(|&i| !is_agu_orig[i]))
252 .collect();
253 let mut newpos = vec![0usize; n];
254 for (new_idx, &old) in order.iter().enumerate() {
255 newpos[old] = new_idx;
256 }
257 let layout: Vec<LoopCell> = order
258 .iter()
259 .map(|&old| remap_cell(&cells[old], &newpos))
260 .collect();
261 let is_agu: Vec<bool> = order.iter().map(|&old| is_agu_orig[old]).collect();
262 let agu_len = is_agu_orig.iter().filter(|&&a| a).count();
263
264 DaeSchedule {
265 n_in,
266 layout,
267 result: remap(result, &newpos),
268 agu_len,
269 is_agu,
270 }
271}
272
273pub fn schedule_for_target(
278 cells: &[LoopCell],
279 result: Src,
280 n_in: usize,
281 target: &FabricTarget,
282) -> DaeSchedule {
283 if target.agu {
284 decouple_access(cells, result, n_in)
285 } else {
286 DaeSchedule {
287 n_in,
288 layout: cells.to_vec(),
289 result,
290 agu_len: 0,
291 is_agu: vec![false; cells.len()],
292 }
293 }
294}
295
296pub fn eval_dae(sched: &DaeSchedule, inputs: &[i64], heap: &mut Vec<i64>) -> i64 {
300 let df = DecisionFabric {
301 n_in: sched.n_in,
302 cells: sched.layout.clone(),
303 result: sched.result,
304 escaped_first_return: false,
305 };
306 eval_decision_fabric_heap(&df, inputs, heap)
307}
308
309fn src_v(s: &Src) -> String {
311 match s {
312 Src::In(k) => format!("in{k}"),
313 Src::Val(k) => format!("v{k}"),
314 Src::Const(c) if *c < 0 => format!("-64'sd{}", (*c as i128).unsigned_abs()),
315 Src::Const(c) => format!("64'sd{c}"),
316 }
317}
318
319pub fn emit_dae_verilog(name: &str, sched: &DaeSchedule, depth: usize) -> String {
325 let idxbits = (usize::BITS - (depth - 1).leading_zeros()) as usize; let inputs = (0..sched.n_in)
327 .map(|k| format!("input [63:0] in{k}"))
328 .collect::<Vec<_>>()
329 .join(", ");
330 let mut body = String::new();
331 for (i, cell) in sched.layout.iter().enumerate() {
332 if i == 0 && sched.agu_len > 0 {
333 body.push_str(" // --- AGU lane (address generation, runs in parallel) ---\n");
334 }
335 if i == sched.agu_len {
336 body.push_str(" // --- exec lane (compute + loads) ---\n");
337 }
338 match cell {
339 LoopCell::Arith { op, a, b } => {
340 body.push_str(&format!(
341 " wire [63:0] v{i} = {};\n",
342 op.verilog(&src_v(a), &src_v(b))
343 ));
344 }
345 LoopCell::Select { cmp, ca, cb, t, e } => {
346 body.push_str(&format!(
347 " wire [63:0] v{i} = ($signed({}) {} $signed({})) ? {} : {};\n",
348 src_v(ca),
349 cmp.verilog(),
350 src_v(cb),
351 src_v(t),
352 src_v(e),
353 ));
354 }
355 LoopCell::HeapRead { addr } => {
356 body.push_str(&format!(
357 " wire signed [63:0] addr{i} = {};\n",
358 src_v(addr)
359 ));
360 body.push_str(&format!(
361 " wire [63:0] v{i} = (addr{i} >= 0 && addr{i} < {depth}) ? heap[addr{i}[{hi}:0]] : 64'd0;\n",
362 hi = idxbits - 1,
363 ));
364 }
365 other => panic!(
366 "emit_dae_verilog: {other:?} is the sequenced-state follow-on (load-only today)"
367 ),
368 }
369 }
370 format!(
371 "// dae_{name} — a decoupled ACCESS/EXECUTE datapath: the AGU lane computes addresses in parallel with\n\
372 // the exec lane; loads read a {depth}-word heap. Combinational, ≡ eval_dae under iverilog.\n\
373 module dae_{name}({inputs}, output [63:0] result);\n\
374 \x20 reg [63:0] heap [0:{maxd}];\n\
375 \x20 initial $readmemh(\"dae_{name}_heap.hex\", heap);\n\
376 {body} assign result = {res};\n\
377 endmodule\n",
378 maxd = depth - 1,
379 res = src_v(&sched.result),
380 )
381}
382
383pub fn emit_dae_stateful_verilog(name: &str, sched: &DaeSchedule, heap_words: usize) -> String {
390 let df = DecisionFabric {
391 n_in: sched.n_in,
392 cells: sched.layout.clone(),
393 result: sched.result,
394 escaped_first_return: false,
395 };
396 emit_state_plane_verilog(name, &df, heap_words)
397}
398
399pub fn emit_dae_testbench(name: &str, n_in: usize, n: usize) -> String {
402 let decls = (0..n_in)
403 .map(|k| format!(" reg [63:0] in{k};\n"))
404 .collect::<String>();
405 let ports = (0..n_in)
406 .map(|k| format!(".in{k}(in{k}), "))
407 .collect::<String>();
408 let drive = (0..n_in)
409 .map(|k| format!(" in{k} = oin[i * {n_in} + {k}];\n"))
410 .collect::<String>();
411 format!(
412 r#"`timescale 1ns/1ps
413module tb_dae_{name};
414{decls} reg [63:0] oin [0:{in_last}];
415 reg [63:0] oout [0:{last}];
416 wire [63:0] result;
417 integer i, errors = 0;
418 dae_{name} dut({ports}.result(result));
419 initial begin
420 $readmemh("dae_{name}_in.hex", oin);
421 $readmemh("dae_{name}_out.hex", oout);
422 for (i = 0; i < {n}; i = i + 1) begin
423{drive} #1;
424 if (result !== oout[i]) begin errors = errors + 1; $display("FAIL vec %0d: got %0d", i, $signed(result)); end
425 end
426 if (errors == 0) $display("PASS: {n} decoupled-datapath evals match eval_dae (≡ the coupled body ≡ VM)");
427 else $display("FAIL: %0d decoupled-datapath mismatches", errors);
428 $finish;
429 end
430endmodule
431"#,
432 in_last = n_in * n - 1,
433 last = n - 1,
434 )
435}