1use super::fabric_rtl::{AluOp, CmpKind, FuncUnit};
18use super::simd::{LaneTy, VecOp};
19
20#[derive(Clone, Copy, Debug, PartialEq)]
23pub enum Src2 {
24 N,
25 W,
26 In(usize),
27 Const(i64),
28 Zero,
29}
30
31#[derive(Clone, Copy, Debug)]
33pub enum CellKind {
34 Arith(AluOp, Src2, Src2),
35 Select(CmpKind, Src2, Src2, Src2, Src2),
36 Func(FuncUnit, Src2, Src2),
42 Simd(VecOp, LaneTy, Src2, Src2),
47 Mem(MemOp, Src2, Src2),
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub enum MemOp {
60 HeapRead,
62 HeapWrite,
64 HeapAlloc,
66}
67
68#[derive(Clone, Copy, Debug)]
70pub enum Drive {
71 Pass(Src2),
72 Alu,
73}
74
75#[derive(Clone, Copy, Debug)]
77pub struct Tile {
78 pub alu: Option<CellKind>,
79 pub vs: Drive,
80 pub he: Drive,
81}
82
83impl Tile {
84 pub const IDLE: Tile = Tile {
86 alu: None,
87 vs: Drive::Pass(Src2::Zero),
88 he: Drive::Pass(Src2::Zero),
89 };
90}
91
92#[derive(Clone, Copy, Debug, PartialEq)]
94pub enum Chan {
95 Vs,
96 He,
97}
98
99#[derive(Clone, Debug)]
102pub struct Grid {
103 pub rows: usize,
104 pub cols: usize,
105 pub n_in: usize,
106 pub tiles: Vec<Vec<Tile>>,
107 pub out: (usize, usize, Chan),
109 pub extra_outs: Vec<(usize, usize, Chan)>,
112}
113
114impl Grid {
115 pub fn new(rows: usize, cols: usize, n_in: usize) -> Grid {
116 Grid {
117 rows,
118 cols,
119 n_in,
120 tiles: vec![vec![Tile::IDLE; cols]; rows],
121 out: (0, 0, Chan::Vs),
122 extra_outs: Vec::new(),
123 }
124 }
125 pub fn all_outs(&self) -> Vec<(usize, usize, Chan)> {
127 std::iter::once(self.out)
128 .chain(self.extra_outs.iter().copied())
129 .collect()
130 }
131 pub fn set(&mut self, r: usize, c: usize, t: Tile) {
132 self.tiles[r][c] = t;
133 }
134 pub fn used_channels(&self) -> usize {
137 let mut n = 0;
138 for row in &self.tiles {
139 for t in row {
140 if !matches!(t.vs, Drive::Pass(Src2::Zero)) {
141 n += 1;
142 }
143 if !matches!(t.he, Drive::Pass(Src2::Zero)) {
144 n += 1;
145 }
146 }
147 }
148 n
149 }
150}
151
152fn read2(s: Src2, n_in: i64, w_in: i64, inputs: &[i64]) -> i64 {
154 match s {
155 Src2::N => n_in,
156 Src2::W => w_in,
157 Src2::In(k) => inputs[k],
158 Src2::Const(c) => c,
159 Src2::Zero => 0,
160 }
161}
162
163fn eval_cell(k: CellKind, n_in: i64, w_in: i64, inputs: &[i64]) -> i64 {
164 let r = |s| read2(s, n_in, w_in, inputs);
165 match k {
166 CellKind::Arith(op, a, b) => op.eval(r(a), r(b)),
167 CellKind::Func(unit, a, b) => unit.eval(r(a), r(b)),
168 CellKind::Simd(op, ty, a, b) => op.eval(ty, r(a) as u64, r(b) as u64) as i64,
169 CellKind::Mem(..) => panic!(
170 "eval_cell: a Mem (state-plane) tile needs the shared heap — use eval_grid_with_heap, not the \
171 stateless eval_grid"
172 ),
173 CellKind::Select(cmp, ca, cb, t, e) => {
174 if cmp.eval(r(ca), r(cb)) {
175 r(t)
176 } else {
177 r(e)
178 }
179 }
180 }
181}
182
183fn grid_channels(g: &Grid, inputs: &[i64]) -> (Vec<Vec<i64>>, Vec<Vec<i64>>) {
186 let mut vs = vec![vec![0i64; g.cols]; g.rows];
187 let mut he = vec![vec![0i64; g.cols]; g.rows];
188 for r in 0..g.rows {
189 for c in 0..g.cols {
190 let n_in = if r == 0 { 0 } else { vs[r - 1][c] };
191 let w_in = if c == 0 { 0 } else { he[r][c - 1] };
192 let t = g.tiles[r][c];
193 let alu = t.alu.map(|k| eval_cell(k, n_in, w_in, inputs)).unwrap_or(0);
194 let drive = |d: Drive| match d {
195 Drive::Pass(s) => read2(s, n_in, w_in, inputs),
196 Drive::Alu => alu,
197 };
198 vs[r][c] = drive(t.vs);
199 he[r][c] = drive(t.he);
200 }
201 }
202 (vs, he)
203}
204
205fn tap(vs: &[Vec<i64>], he: &[Vec<i64>], o: (usize, usize, Chan)) -> i64 {
206 let (r, c, ch) = o;
207 match ch {
208 Chan::Vs => vs[r][c],
209 Chan::He => he[r][c],
210 }
211}
212
213pub fn eval_grid(g: &Grid, inputs: &[i64]) -> i64 {
215 let (vs, he) = grid_channels(g, inputs);
216 tap(&vs, &he, g.out)
217}
218
219pub fn eval_grid_all(g: &Grid, inputs: &[i64]) -> Vec<i64> {
221 let (vs, he) = grid_channels(g, inputs);
222 g.all_outs().into_iter().map(|o| tap(&vs, &he, o)).collect()
223}
224
225pub fn eval_grid_with_heap(g: &Grid, inputs: &[i64]) -> i64 {
231 let mut heap: Vec<i64> = Vec::new();
232 let (vs, he) = grid_channels_with_heap(g, inputs, &mut heap);
233 tap(&vs, &he, g.out)
234}
235
236fn grid_channels_with_heap(
241 g: &Grid,
242 inputs: &[i64],
243 heap: &mut Vec<i64>,
244) -> (Vec<Vec<i64>>, Vec<Vec<i64>>) {
245 let mut vs = vec![vec![0i64; g.cols]; g.rows];
246 let mut he = vec![vec![0i64; g.cols]; g.rows];
247 for r in 0..g.rows {
248 for c in 0..g.cols {
249 let n = if r == 0 { 0 } else { vs[r - 1][c] };
250 let w = if c == 0 { 0 } else { he[r][c - 1] };
251 let t = g.tiles[r][c];
252 let alu = match t.alu {
253 Some(CellKind::Mem(op, a, b)) => {
254 let av = read2(a, n, w, inputs);
255 match op {
256 MemOp::HeapRead => heap.get(av.max(0) as usize).copied().unwrap_or(0),
257 MemOp::HeapWrite => {
258 let bv = read2(b, n, w, inputs);
259 let ai = av.max(0) as usize;
260 if ai >= heap.len() {
261 heap.resize(ai + 1, 0);
262 }
263 heap[ai] = bv;
264 bv
265 }
266 MemOp::HeapAlloc => {
267 let ptr = heap.len() as i64;
268 heap.resize(heap.len() + av.max(0) as usize, 0);
269 ptr
270 }
271 }
272 }
273 Some(k) => eval_cell(k, n, w, inputs),
274 None => 0,
275 };
276 vs[r][c] = match t.vs {
277 Drive::Pass(s) => read2(s, n, w, inputs),
278 Drive::Alu => alu,
279 };
280 he[r][c] = match t.he {
281 Drive::Pass(s) => read2(s, n, w, inputs),
282 Drive::Alu => alu,
283 };
284 }
285 }
286 (vs, he)
287}
288
289pub fn eval_grid_all_with_heap(g: &Grid, inputs: &[i64], heap: &mut Vec<i64>) -> Vec<i64> {
294 let (vs, he) = grid_channels_with_heap(g, inputs, heap);
295 g.all_outs().into_iter().map(|o| tap(&vs, &he, o)).collect()
296}
297
298pub fn run_program_cfg_on_grid(pf: &super::fabric_rtl::ProgramCfgFabric, params: &[i64]) -> i64 {
308 use super::fabric_rtl::{Src as FSrc, Term};
309 use std::collections::HashMap;
310 let mut file: HashMap<u16, i64> = (0..pf.n_params).map(|p| (p as u16, params[p])).collect();
311 let mut heap: Vec<i64> = Vec::new();
314 let mut blk = 0usize;
315 for _ in 0..10_000_000 {
316 let b = &pf.blocks[blk];
317 let inputs: Vec<i64> = b
318 .inputs
319 .iter()
320 .map(|r| *file.get(r).unwrap_or(&0))
321 .collect();
322 let mut needed: Vec<FSrc> = b.writes.iter().map(|(_, s)| *s).collect();
325 let term_off = needed.len();
326 match &b.term {
327 Term::Jump(_) => {}
328 Term::Branch { cond, .. } => needed.push(*cond),
329 Term::Return(s) => needed.push(*s),
330 }
331 let g = route_loop_body(&b.body, b.inputs.len(), &needed);
332 let outs = eval_grid_all_with_heap(&g, &inputs, &mut heap);
333 for (i, (r, _)) in b.writes.iter().enumerate() {
334 file.insert(*r, outs[i]);
335 }
336 match &b.term {
337 Term::Jump(t) => blk = *t,
338 Term::Branch {
339 then_blk, else_blk, ..
340 } => {
341 blk = if outs[term_off] != 0 {
342 *then_blk
343 } else {
344 *else_blk
345 }
346 }
347 Term::Return(_) => return outs[term_off],
348 }
349 }
350 panic!(
351 "cfg grid: controller did not return within the step budget (a back-edge slipped through?)"
352 )
353}
354
355fn src2_v(s: Src2, n: &str, w: &str) -> String {
357 match s {
358 Src2::N => n.to_string(),
359 Src2::W => w.to_string(),
360 Src2::In(k) => format!("in{k}"),
361 Src2::Const(c) => {
362 if c < 0 {
363 format!("-64'sd{}", (c as i128).unsigned_abs())
364 } else {
365 format!("64'sd{c}")
366 }
367 }
368 Src2::Zero => "64'sd0".to_string(),
369 }
370}
371
372fn cell_v(k: CellKind, n: &str, w: &str) -> String {
373 let s = |x| src2_v(x, n, w);
374 match k {
375 CellKind::Arith(op, a, b) => op.verilog(&s(a), &s(b)),
376 CellKind::Func(unit, a, b) => unit.verilog(&s(a), &s(b)),
377 CellKind::Simd(op, ty, a, b) => op.verilog(ty, &s(a), &s(b)),
378 CellKind::Mem(..) => panic!(
379 "cell_v: Mem-tile RTL (a shared BRAM read/write + a sequencing controller) is the X2b follow-on; \
380 X2a proves the state plane at the eval level via eval_grid_with_heap"
381 ),
382 CellKind::Select(cmp, ca, cb, t, e) => {
383 format!("($signed({}) {} $signed({})) ? {} : {}", s(ca), cmp.verilog(), s(cb), s(t), s(e))
384 }
385 }
386}
387
388pub fn emit_grid_verilog(name: &str, g: &Grid) -> String {
391 let inputs = (0..g.n_in)
392 .map(|k| format!("input [63:0] in{k}"))
393 .collect::<Vec<_>>()
394 .join(", ");
395 let inputs = if inputs.is_empty() {
396 String::new()
397 } else {
398 format!("{inputs}, ")
399 };
400 let mut body = String::new();
401 for r in 0..g.rows {
402 for c in 0..g.cols {
403 let nn = if r == 0 {
404 "64'sd0".to_string()
405 } else {
406 format!("vs_{}_{c}", r - 1)
407 };
408 let ww = if c == 0 {
409 "64'sd0".to_string()
410 } else {
411 format!("he_{r}_{}", c - 1)
412 };
413 let t = g.tiles[r][c];
414 if let Some(k) = t.alu {
415 body.push_str(&format!(
416 " wire [63:0] alu_{r}_{c} = {};\n",
417 cell_v(k, &nn, &ww)
418 ));
419 }
420 let drv = |d: Drive| match d {
421 Drive::Pass(s) => src2_v(s, &nn, &ww),
422 Drive::Alu => format!("alu_{r}_{c}"),
423 };
424 body.push_str(&format!(" wire [63:0] vs_{r}_{c} = {};\n", drv(t.vs)));
425 body.push_str(&format!(" wire [63:0] he_{r}_{c} = {};\n", drv(t.he)));
426 }
427 }
428 let outs = g.all_outs();
429 let out_wire = |o: (usize, usize, Chan)| match o.2 {
430 Chan::Vs => format!("vs_{}_{}", o.0, o.1),
431 Chan::He => format!("he_{}_{}", o.0, o.1),
432 };
433 let (ports, assigns) = if outs.len() == 1 {
435 (
436 "output [63:0] result".to_string(),
437 format!(" assign result = {};\n", out_wire(outs[0])),
438 )
439 } else {
440 let ports = (0..outs.len())
441 .map(|j| format!("output [63:0] out{j}"))
442 .collect::<Vec<_>>()
443 .join(", ");
444 let assigns = outs
445 .iter()
446 .enumerate()
447 .map(|(j, o)| format!(" assign out{j} = {};\n", out_wire(*o)))
448 .collect::<String>();
449 (ports, assigns)
450 };
451 format!(
452 "// grid_{name} — a fabric placed on a {rows}×{cols} CGRA tile array with a two-channel\n\
453 // switchbox interconnect (independent vertical `vs` + horizontal `he` wires per tile,\n\
454 // nearest-neighbour only). Feed-forward south/east, so combinational. The placed+routed\n\
455 // array is certified == the abstract datapath by our own SAT.\n\
456 module grid_{name}({inputs}{ports});\n\
457 {body}{assigns}\
458 endmodule\n",
459 rows = g.rows,
460 cols = g.cols,
461 )
462}
463
464pub fn grid_to_bounded(g: &Grid, width: u32) -> crate::codegen_sva::sva_to_verify::BoundedExpr {
466 grid_output_to_bounded(g, 0, width, &|k| format!("in{k}"))
467}
468
469pub trait Datapath {
480 type V: Clone;
482 fn input(&mut self, k: usize) -> Self::V;
484 fn constant(&mut self, c: i64) -> Self::V;
486 fn arith(&mut self, op: AluOp, a: Self::V, b: Self::V) -> Self::V;
488 fn select(&mut self, cmp: CmpKind, ca: Self::V, cb: Self::V, t: Self::V, e: Self::V)
490 -> Self::V;
491}
492
493fn alu_bounded_op(op: AluOp) -> crate::codegen_sva::sva_to_verify::BitVecBoundedOp {
496 use crate::codegen_sva::sva_to_verify::BitVecBoundedOp as B;
497 match op {
498 AluOp::Add => B::Add,
499 AluOp::Sub => B::Sub,
500 AluOp::Mul => B::Mul,
501 AluOp::And => B::And,
502 AluOp::Or => B::Or,
503 AluOp::Xor => B::Xor,
504 AluOp::Shl => B::Shl,
505 AluOp::Shr => B::AShr,
506 AluOp::UShr => B::Shr,
507 }
508}
509
510fn select_form(
514 cmp: CmpKind,
515) -> (
516 crate::codegen_sva::sva_to_verify::BitVecBoundedOp,
517 bool,
518 bool,
519) {
520 use crate::codegen_sva::sva_to_verify::BitVecBoundedOp as B;
521 match cmp {
523 CmpKind::Lt => (B::SLt, false, false),
524 CmpKind::Gt => (B::SLt, true, false),
525 CmpKind::Ge => (B::SLt, false, true),
526 CmpKind::Le => (B::SLt, true, true),
527 CmpKind::Eq => (B::Eq, false, false),
528 CmpKind::Ne => (B::Eq, false, true),
529 }
530}
531
532pub struct BoundedBuild<'a> {
534 width: u32,
535 name_in: &'a dyn Fn(usize) -> String,
536}
537
538impl<'a> BoundedBuild<'a> {
539 pub fn new(width: u32, name_in: &'a dyn Fn(usize) -> String) -> Self {
541 BoundedBuild { width, name_in }
542 }
543}
544
545impl Datapath for BoundedBuild<'_> {
546 type V = crate::codegen_sva::sva_to_verify::BoundedExpr;
547 fn input(&mut self, k: usize) -> Self::V {
548 crate::codegen_sva::sva_to_verify::BoundedExpr::BitVecVar((self.name_in)(k), self.width)
549 }
550 fn constant(&mut self, c: i64) -> Self::V {
551 let mask = if self.width >= 64 {
552 u64::MAX
553 } else {
554 (1u64 << self.width) - 1
555 };
556 crate::codegen_sva::sva_to_verify::BoundedExpr::BitVecConst {
557 width: self.width,
558 value: (c as u64) & mask,
559 }
560 }
561 fn arith(&mut self, op: AluOp, a: Self::V, b: Self::V) -> Self::V {
562 crate::codegen_sva::sva_to_verify::BoundedExpr::BitVecBinary {
563 op: alu_bounded_op(op),
564 left: Box::new(a),
565 right: Box::new(b),
566 }
567 }
568 fn select(
569 &mut self,
570 cmp: CmpKind,
571 ca: Self::V,
572 cb: Self::V,
573 t: Self::V,
574 e: Self::V,
575 ) -> Self::V {
576 use crate::codegen_sva::sva_to_verify::BoundedExpr;
577 let (op, swap_cmp, swap_arms) = select_form(cmp);
578 let (l, r) = if swap_cmp { (cb, ca) } else { (ca, cb) };
579 let (then, els) = if swap_arms { (e, t) } else { (t, e) };
580 BoundedExpr::BitVecIte {
581 cond: Box::new(BoundedExpr::BitVecBinary {
582 op,
583 left: Box::new(l),
584 right: Box::new(r),
585 }),
586 then: Box::new(then),
587 else_: Box::new(els),
588 }
589 }
590}
591
592pub struct GateBuild<'a> {
595 width: u32,
596 name_in: &'a dyn Fn(usize) -> String,
597}
598
599impl<'a> GateBuild<'a> {
600 pub fn new(width: u32, name_in: &'a dyn Fn(usize) -> String) -> Self {
602 GateBuild { width, name_in }
603 }
604}
605
606impl Datapath for GateBuild<'_> {
607 type V = crate::codegen_sva::bitblast::Word;
608 fn input(&mut self, k: usize) -> Self::V {
609 crate::codegen_sva::bitblast::word_var(&(self.name_in)(k), self.width)
610 }
611 fn constant(&mut self, c: i64) -> Self::V {
612 crate::codegen_sva::bitblast::word_const(self.width, c as u64)
613 }
614 fn arith(&mut self, op: AluOp, a: Self::V, b: Self::V) -> Self::V {
615 crate::codegen_sva::bitblast::word_binary(alu_bounded_op(op), &a, &b)
616 .expect("every operand of a grid tile carries the grid's one width")
617 }
618 fn select(
619 &mut self,
620 cmp: CmpKind,
621 ca: Self::V,
622 cb: Self::V,
623 t: Self::V,
624 e: Self::V,
625 ) -> Self::V {
626 let (op, swap_cmp, swap_arms) = select_form(cmp);
627 let (l, r) = if swap_cmp { (cb, ca) } else { (ca, cb) };
628 let (then, els) = if swap_arms { (e, t) } else { (t, e) };
629 let pred = crate::codegen_sva::bitblast::word_compare(op, &l, &r)
630 .expect("every operand of a grid tile carries the grid's one width");
631 crate::codegen_sva::bitblast::word_select(&pred, &then, &els)
632 .expect("every operand of a grid tile carries the grid's one width")
633 }
634}
635
636const NO_BITBLAST_TILE: &str =
639 "grid SAT: a Func (division/float), Mem (state-plane), or Simd (packed) tile has no bitblast \
640 lowering here — such a grid is proven by eval ≡ VM + iverilog, not by our SAT";
641
642fn tile_outputs<B: Datapath>(b: &mut B, t: Tile, n: &B::V, w: &B::V) -> Option<(B::V, B::V)> {
645 let src2 = |b: &mut B, s: Src2| -> B::V {
646 match s {
647 Src2::N => n.clone(),
648 Src2::W => w.clone(),
649 Src2::In(k) => b.input(k),
650 Src2::Const(c) => b.constant(c),
651 Src2::Zero => b.constant(0),
652 }
653 };
654 let alu = match t.alu {
655 None => None,
656 Some(CellKind::Arith(op, x, y)) => {
657 let (x, y) = (src2(b, x), src2(b, y));
658 Some(b.arith(op, x, y))
659 }
660 Some(CellKind::Select(cmp, ca, cb, tt, ee)) => {
661 let (ca, cb, tt, ee) = (src2(b, ca), src2(b, cb), src2(b, tt), src2(b, ee));
662 Some(b.select(cmp, ca, cb, tt, ee))
663 }
664 Some(CellKind::Func(..)) | Some(CellKind::Mem(..)) | Some(CellKind::Simd(..)) => {
665 return None
666 }
667 };
668 let drive = |b: &mut B, d: Drive| -> B::V {
669 match d {
670 Drive::Pass(s) => src2(b, s),
671 Drive::Alu => alu.clone().expect("Drive::Alu on a tile with no compute"),
672 }
673 };
674 let vs = drive(b, t.vs);
675 let he = drive(b, t.he);
676 Some((vs, he))
677}
678
679pub fn grid_output_datapath<B: Datapath>(b: &mut B, g: &Grid, which: usize) -> Option<B::V> {
683 let zero = b.constant(0);
684 let mut vs: Vec<Vec<B::V>> = vec![vec![zero.clone(); g.cols]; g.rows];
685 let mut he: Vec<Vec<B::V>> = vec![vec![zero.clone(); g.cols]; g.rows];
686 for r in 0..g.rows {
687 for c in 0..g.cols {
688 let n = if r == 0 {
689 zero.clone()
690 } else {
691 vs[r - 1][c].clone()
692 };
693 let w = if c == 0 {
694 zero.clone()
695 } else {
696 he[r][c - 1].clone()
697 };
698 let (v, h) = tile_outputs(b, g.tiles[r][c], &n, &w)?;
699 vs[r][c] = v;
700 he[r][c] = h;
701 }
702 }
703 let (or, oc, ch) = g.all_outs()[which];
704 Some(match ch {
705 Chan::Vs => vs[or][oc].clone(),
706 Chan::He => he[or][oc].clone(),
707 })
708}
709
710pub fn grid_output_to_gates(
715 g: &Grid,
716 which: usize,
717 width: u32,
718 name_in: &dyn Fn(usize) -> String,
719) -> Option<crate::codegen_sva::bitblast::Word> {
720 grid_output_datapath(&mut GateBuild::new(width, name_in), g, which)
721}
722
723pub fn cells_datapath<B: Datapath>(
727 b: &mut B,
728 cells: &[super::fabric_rtl::LoopCell],
729 result: super::fabric_rtl::Src,
730) -> Option<B::V> {
731 use super::fabric_rtl::{LoopCell, Src as FSrc};
732 fn src<B: Datapath>(b: &mut B, s: FSrc, vals: &[B::V]) -> Option<B::V> {
733 Some(match s {
734 FSrc::In(k) => b.input(k),
735 FSrc::Val(k) => vals.get(k)?.clone(),
736 FSrc::Const(c) => b.constant(c),
737 })
738 }
739 let mut vals: Vec<B::V> = Vec::with_capacity(cells.len());
740 for c in cells {
741 let v = match c {
742 LoopCell::Arith { op, a, b: rhs } => {
743 let (x, y) = (src(b, *a, &vals)?, src(b, *rhs, &vals)?);
744 b.arith(*op, x, y)
745 }
746 LoopCell::Select { cmp, ca, cb, t, e } => {
747 let (ca, cb, t, e) = (
748 src(b, *ca, &vals)?,
749 src(b, *cb, &vals)?,
750 src(b, *t, &vals)?,
751 src(b, *e, &vals)?,
752 );
753 b.select(*cmp, ca, cb, t, e)
754 }
755 _ => return None,
756 };
757 vals.push(v);
758 }
759 src(b, result, &vals)
760}
761
762pub fn cells_to_gates(
767 cells: &[super::fabric_rtl::LoopCell],
768 result: super::fabric_rtl::Src,
769 width: u32,
770) -> Option<crate::codegen_sva::bitblast::Word> {
771 cells_datapath(
772 &mut GateBuild::new(width, &|k| format!("in{k}")),
773 cells,
774 result,
775 )
776}
777
778pub fn tile_channel_demand(cell: &super::fabric_rtl::LoopCell) -> usize {
787 routed_producers(cell).len()
788}
789
790pub const TILE_OPERAND_CHANNELS: usize = 2;
792
793fn routed_producers(cell: &super::fabric_rtl::LoopCell) -> Vec<usize> {
796 use super::fabric_rtl::{LoopCell, Src as FSrc};
797 let ports: Vec<super::fabric_rtl::Src> = match *cell {
798 LoopCell::Arith { a, b, .. } | LoopCell::Func { a, b, .. } => vec![a, b],
799 LoopCell::VectorArith { a, b, .. } => vec![a, b],
800 LoopCell::Select { ca, cb, t, e, .. } => vec![ca, cb, t, e],
801 LoopCell::HeapRead { addr } => vec![addr],
802 LoopCell::HeapWrite { addr, val } => vec![addr, val],
803 LoopCell::HeapAlloc { size } => vec![size],
804 LoopCell::MemRead { addr } => vec![addr],
805 };
806 let mut out: Vec<usize> = Vec::new();
807 for p in ports {
808 if let FSrc::Val(j) = p {
809 if !out.contains(&j) {
810 out.push(j);
811 }
812 }
813 }
814 out
815}
816
817pub fn unroutable_cell(cells: &[super::fabric_rtl::LoopCell]) -> Option<(usize, usize)> {
822 cells.iter().enumerate().find_map(|(i, c)| {
823 let d = tile_channel_demand(c);
824 (d > TILE_OPERAND_CHANNELS).then_some((i, d))
825 })
826}
827
828pub fn grid_output_to_bounded(
832 g: &Grid,
833 which: usize,
834 width: u32,
835 name_in: &dyn Fn(usize) -> String,
836) -> crate::codegen_sva::sva_to_verify::BoundedExpr {
837 grid_output_datapath(&mut BoundedBuild::new(width, name_in), g, which).expect(NO_BITBLAST_TILE)
838}
839
840pub fn route_straight_line(
848 cells: &[super::fabric_rtl::FabricOp],
849 n_in: usize,
850 result: usize,
851) -> Grid {
852 use super::fabric_rtl::Src as FSrc;
853 let m = cells.len().max(1);
854 let cols = m;
855 let rows = 2 * m + 2;
856 let r_of = |i: usize| 2 * (i + 1);
857 let mut g = Grid::new(rows, cols, n_in);
858
859 let operand = |s: FSrc| -> (Src2, Option<usize>) {
861 match s {
862 FSrc::In(k) => (Src2::In(k), None),
863 FSrc::Const(c) => (Src2::Const(c), None),
864 FSrc::Val(j) => (Src2::Zero, Some(j)), }
866 };
867 for (i, cell) in cells.iter().enumerate() {
868 let (ri, ci) = (r_of(i), i);
869 let (mut sa, route_a) = operand(cell.a);
870 let (mut sb, route_b) = operand(cell.b);
871 if route_a.is_some() {
873 sa = Src2::N;
874 }
875 if route_b.is_some() {
876 sb = Src2::W;
877 }
878 g.tiles[ri][ci].alu = Some(CellKind::Arith(cell.op, sa, sb));
879 g.tiles[ri][ci].vs = Drive::Alu;
880
881 if let Some(a) = route_a {
884 let rr = ri - 1;
885 g.tiles[rr][a].he = Drive::Pass(Src2::N); for c in a + 1..ci {
887 g.tiles[rr][c].he = Drive::Pass(Src2::W); }
889 g.tiles[rr][ci].vs = Drive::Pass(Src2::W); }
891 if let Some(b) = route_b {
893 let rr = ri;
894 g.tiles[rr][b].he = Drive::Pass(Src2::N);
895 for c in b + 1..ci {
896 g.tiles[rr][c].he = Drive::Pass(Src2::W);
897 }
898 }
899 }
900
901 for i in 0..cells.len() {
903 let (ri, ci) = (r_of(i), i);
904 for r in ri + 1..rows {
905 if matches!(g.tiles[r][ci].vs, Drive::Pass(Src2::Zero)) {
907 g.tiles[r][ci].vs = Drive::Pass(Src2::N);
908 }
909 }
910 }
911
912 g.out = (r_of(result), result, Chan::Vs);
913 g
914}
915
916fn route_north(g: &mut Grid, from: Option<usize>, ci: usize, ri: usize) {
919 if let Some(a) = from {
920 let rr = ri - 1;
921 g.tiles[rr][a].he = Drive::Pass(Src2::N);
922 for c in a + 1..ci {
923 g.tiles[rr][c].he = Drive::Pass(Src2::W);
924 }
925 g.tiles[rr][ci].vs = Drive::Pass(Src2::W);
926 }
927}
928
929fn route_west(g: &mut Grid, from: Option<usize>, ci: usize, ri: usize) {
932 if let Some(b) = from {
933 let rr = ri;
934 g.tiles[rr][b].he = Drive::Pass(Src2::N);
935 for c in b + 1..ci {
936 g.tiles[rr][c].he = Drive::Pass(Src2::W);
937 }
938 }
939}
940
941pub fn route_loop_body(
950 cells: &[super::fabric_rtl::LoopCell],
951 n_in: usize,
952 next: &[super::fabric_rtl::Src],
953) -> Grid {
954 use super::fabric_rtl::{LoopCell, Src as FSrc};
955 let m = cells.len().max(1);
956 let n_src_out = next.iter().filter(|s| !matches!(s, FSrc::Val(_))).count();
957 let cols = m + n_src_out.max(0);
958 let rows = 2 * m + 2;
959 let r_of = |i: usize| 2 * (i + 1);
960 let mut g = Grid::new(rows, cols.max(1), n_in);
961 let operand = |s: FSrc| -> (Src2, Option<usize>) {
962 match s {
963 FSrc::In(k) => (Src2::In(k), None),
964 FSrc::Const(c) => (Src2::Const(c), None),
965 FSrc::Val(j) => (Src2::Zero, Some(j)),
966 }
967 };
968 for (i, cell) in cells.iter().enumerate() {
969 let (ri, ci) = (r_of(i), i);
970 match *cell {
971 LoopCell::Arith { op, a, b } => {
972 let (mut sa, ra) = operand(a);
973 let (mut sb, rb) = operand(b);
974 if ra.is_some() {
975 sa = Src2::N;
976 }
977 if rb.is_some() {
978 sb = Src2::W;
979 }
980 g.tiles[ri][ci].alu = Some(CellKind::Arith(op, sa, sb));
981 g.tiles[ri][ci].vs = Drive::Alu;
982 route_north(&mut g, ra, ci, ri);
983 route_west(&mut g, rb, ci, ri);
984 }
985 LoopCell::Func { unit, a, b } => {
990 let (mut sa, ra) = operand(a);
991 let (mut sb, rb) = operand(b);
992 if ra.is_some() {
993 sa = Src2::N;
994 }
995 if rb.is_some() {
996 sb = Src2::W;
997 }
998 g.tiles[ri][ci].alu = Some(CellKind::Func(unit, sa, sb));
999 g.tiles[ri][ci].vs = Drive::Alu;
1000 route_north(&mut g, ra, ci, ri);
1001 route_west(&mut g, rb, ci, ri);
1002 }
1003 LoopCell::VectorArith { op, ty, a, b } => {
1007 let (mut sa, ra) = operand(a);
1008 let (mut sb, rb) = operand(b);
1009 if ra.is_some() {
1010 sa = Src2::N;
1011 }
1012 if rb.is_some() {
1013 sb = Src2::W;
1014 }
1015 g.tiles[ri][ci].alu = Some(CellKind::Simd(op, ty, sa, sb));
1016 g.tiles[ri][ci].vs = Drive::Alu;
1017 route_north(&mut g, ra, ci, ri);
1018 route_west(&mut g, rb, ci, ri);
1019 }
1020 LoopCell::HeapRead { addr } => {
1025 let (mut sa, ra) = operand(addr);
1026 if ra.is_some() {
1027 sa = Src2::N;
1028 }
1029 g.tiles[ri][ci].alu = Some(CellKind::Mem(MemOp::HeapRead, sa, Src2::Zero));
1030 g.tiles[ri][ci].vs = Drive::Alu;
1031 route_north(&mut g, ra, ci, ri);
1032 }
1033 LoopCell::HeapWrite { addr, val } => {
1034 let (mut sa, ra) = operand(addr);
1035 let (mut sb, rb) = operand(val);
1036 if ra.is_some() {
1037 sa = Src2::N;
1038 }
1039 if rb.is_some() {
1040 sb = Src2::W;
1041 }
1042 g.tiles[ri][ci].alu = Some(CellKind::Mem(MemOp::HeapWrite, sa, sb));
1043 g.tiles[ri][ci].vs = Drive::Alu;
1044 route_north(&mut g, ra, ci, ri);
1045 route_west(&mut g, rb, ci, ri);
1046 }
1047 LoopCell::HeapAlloc { size } => {
1048 let (mut sa, ra) = operand(size);
1049 if ra.is_some() {
1050 sa = Src2::N;
1051 }
1052 g.tiles[ri][ci].alu = Some(CellKind::Mem(MemOp::HeapAlloc, sa, Src2::Zero));
1053 g.tiles[ri][ci].vs = Drive::Alu;
1054 route_north(&mut g, ra, ci, ri);
1055 }
1056 LoopCell::MemRead { .. } => panic!(
1059 "grid: a loop-sequence MemRead is the induction-counter scratchpad (P2, eval_loop_fabric_seq), \
1060 distinct from the Stage-X2 shared-heap state plane"
1061 ),
1062 LoopCell::Select { cmp, ca, cb, t, e } => {
1070 let routed = routed_producers(cell);
1071 assert!(
1072 routed.len() <= TILE_OPERAND_CHANNELS,
1073 "grid: a tile has {TILE_OPERAND_CHANNELS} operand channels (north and west) \
1074 but this mux needs {} distinct computed values routed to it — check \
1075 `unroutable_cell` before placing",
1076 routed.len()
1077 );
1078 let chan = |s: FSrc| -> Src2 {
1079 match s {
1080 FSrc::In(k) => Src2::In(k),
1081 FSrc::Const(c) => Src2::Const(c),
1082 FSrc::Val(j) => {
1083 if routed[0] == j {
1084 Src2::N
1085 } else {
1086 Src2::W
1087 }
1088 }
1089 }
1090 };
1091 g.tiles[ri][ci].alu =
1092 Some(CellKind::Select(cmp, chan(ca), chan(cb), chan(t), chan(e)));
1093 g.tiles[ri][ci].vs = Drive::Alu;
1094 route_north(&mut g, routed.first().copied(), ci, ri);
1095 route_west(&mut g, routed.get(1).copied(), ci, ri);
1096 }
1097 }
1098 }
1099 for i in 0..cells.len() {
1100 let (ri, ci) = (r_of(i), i);
1101 for r in ri + 1..rows {
1102 if matches!(g.tiles[r][ci].vs, Drive::Pass(Src2::Zero)) {
1103 g.tiles[r][ci].vs = Drive::Pass(Src2::N);
1104 }
1105 }
1106 }
1107 let mut extra_col = m;
1110 let mut outs: Vec<(usize, usize, Chan)> = Vec::new();
1111 for s in next {
1112 match s {
1113 FSrc::Val(k) => outs.push((r_of(*k), *k, Chan::Vs)),
1114 FSrc::In(k) => {
1115 g.tiles[0][extra_col].vs = Drive::Pass(Src2::In(*k));
1116 outs.push((0, extra_col, Chan::Vs));
1117 extra_col += 1;
1118 }
1119 FSrc::Const(c) => {
1120 g.tiles[0][extra_col].vs = Drive::Pass(Src2::Const(*c));
1121 outs.push((0, extra_col, Chan::Vs));
1122 extra_col += 1;
1123 }
1124 }
1125 }
1126 if outs.is_empty() {
1131 outs.push((0, 0, Chan::Vs));
1132 }
1133 g.out = outs[0];
1134 g.extra_outs = outs[1..].to_vec();
1135 g
1136}
1137
1138fn tile_bounded(
1143 t: Tile,
1144 n: &crate::codegen_sva::sva_to_verify::BoundedExpr,
1145 w: &crate::codegen_sva::sva_to_verify::BoundedExpr,
1146 width: u32,
1147 name_in: &dyn Fn(usize) -> String,
1148) -> (
1149 crate::codegen_sva::sva_to_verify::BoundedExpr,
1150 crate::codegen_sva::sva_to_verify::BoundedExpr,
1151) {
1152 tile_outputs(&mut BoundedBuild::new(width, name_in), t, n, w).expect(NO_BITBLAST_TILE)
1153}
1154
1155pub fn time_mux_bounded(
1160 a: &Grid,
1161 b: &Grid,
1162 width: u32,
1163) -> crate::codegen_sva::sva_to_verify::BoundedExpr {
1164 use crate::codegen_sva::sva_to_verify::{BitVecBoundedOp, BoundedExpr};
1165 assert!(
1166 a.rows == b.rows && a.cols == b.cols,
1167 "time-mux requires equal grid dimensions"
1168 );
1169 let name_in = |k: usize| format!("in{k}");
1170 let konst = |c: i64| BoundedExpr::BitVecConst {
1171 width,
1172 value: c as u64,
1173 };
1174 let is_a = BoundedExpr::BitVecBinary {
1175 op: BitVecBoundedOp::Eq,
1176 left: Box::new(BoundedExpr::BitVecVar("ctx".to_string(), 1)),
1177 right: Box::new(BoundedExpr::BitVecConst { width: 1, value: 1 }),
1178 };
1179 let mux = |va: BoundedExpr, vb: BoundedExpr| BoundedExpr::BitVecIte {
1180 cond: Box::new(is_a.clone()),
1181 then: Box::new(va),
1182 else_: Box::new(vb),
1183 };
1184 let zero = konst(0);
1185 let mut vs = vec![vec![zero.clone(); a.cols]; a.rows];
1186 let mut he = vec![vec![zero.clone(); a.cols]; a.rows];
1187 for r in 0..a.rows {
1188 for c in 0..a.cols {
1189 let n = if r == 0 {
1190 zero.clone()
1191 } else {
1192 vs[r - 1][c].clone()
1193 };
1194 let w = if c == 0 {
1195 zero.clone()
1196 } else {
1197 he[r][c - 1].clone()
1198 };
1199 let (a_vs, a_he) = tile_bounded(a.tiles[r][c], &n, &w, width, &name_in);
1200 let (b_vs, b_he) = tile_bounded(b.tiles[r][c], &n, &w, width, &name_in);
1201 vs[r][c] = mux(a_vs, b_vs);
1202 he[r][c] = mux(a_he, b_he);
1203 }
1204 }
1205 let tap = |o: (usize, usize, Chan)| match o.2 {
1206 Chan::Vs => vs[o.0][o.1].clone(),
1207 Chan::He => he[o.0][o.1].clone(),
1208 };
1209 mux(tap(a.out), tap(b.out))
1210}
1211
1212pub fn time_mux_n_bounded(
1221 grids: &[&Grid],
1222 config: u64,
1223 width: u32,
1224 cfg_width: u32,
1225) -> crate::codegen_sva::sva_to_verify::BoundedExpr {
1226 use crate::codegen_sva::sva_to_verify::{BitVecBoundedOp, BoundedExpr};
1227 assert!(!grids.is_empty(), "time-mux-n needs at least one fabric");
1228 let rows = grids[0].rows;
1229 let cols = grids[0].cols;
1230 for g in grids {
1231 assert!(
1232 g.rows == rows && g.cols == cols,
1233 "time-mux-n requires equal grid dimensions"
1234 );
1235 }
1236 let name_in = |k: usize| format!("in{k}");
1237 let konst = |c: i64| BoundedExpr::BitVecConst {
1238 width,
1239 value: c as u64,
1240 };
1241 let cfg_is = |k: usize| BoundedExpr::BitVecBinary {
1243 op: BitVecBoundedOp::Eq,
1244 left: Box::new(BoundedExpr::BitVecConst {
1245 width: cfg_width,
1246 value: config,
1247 }),
1248 right: Box::new(BoundedExpr::BitVecConst {
1249 width: cfg_width,
1250 value: k as u64,
1251 }),
1252 };
1253 let mux_n = |vs: &[BoundedExpr]| -> BoundedExpr {
1255 let mut acc = vs[vs.len() - 1].clone();
1256 for k in (0..vs.len() - 1).rev() {
1257 acc = BoundedExpr::BitVecIte {
1258 cond: Box::new(cfg_is(k)),
1259 then: Box::new(vs[k].clone()),
1260 else_: Box::new(acc),
1261 };
1262 }
1263 acc
1264 };
1265 let zero = konst(0);
1266 let mut vs = vec![vec![zero.clone(); cols]; rows];
1267 let mut he = vec![vec![zero.clone(); cols]; rows];
1268 for r in 0..rows {
1269 for c in 0..cols {
1270 let n = if r == 0 {
1271 zero.clone()
1272 } else {
1273 vs[r - 1][c].clone()
1274 };
1275 let w = if c == 0 {
1276 zero.clone()
1277 } else {
1278 he[r][c - 1].clone()
1279 };
1280 let mut dvs = Vec::with_capacity(grids.len());
1281 let mut dhe = Vec::with_capacity(grids.len());
1282 for g in grids {
1283 let (gvs, ghe) = tile_bounded(g.tiles[r][c], &n, &w, width, &name_in);
1284 dvs.push(gvs);
1285 dhe.push(ghe);
1286 }
1287 vs[r][c] = mux_n(&dvs);
1288 he[r][c] = mux_n(&dhe);
1289 }
1290 }
1291 let tap = |o: (usize, usize, Chan)| match o.2 {
1292 Chan::Vs => vs[o.0][o.1].clone(),
1293 Chan::He => he[o.0][o.1].clone(),
1294 };
1295 let taps: Vec<BoundedExpr> = grids.iter().map(|g| tap(g.out)).collect();
1296 mux_n(&taps)
1297}
1298
1299pub fn certify_reconfig(sources: &[(&str, &str)], width: u32, cfg_width: u32) -> Vec<bool> {
1306 use crate::codegen_sva::bitblast::lower_bool;
1307 use crate::codegen_sva::fabric_rtl::residual_from_source;
1308 use crate::codegen_sva::sva_to_verify::BoundedExpr;
1309 use logicaffeine_proof::sat::{prove_unsat, UnsatOutcome};
1310 use logicaffeine_proof::ProofExpr;
1311
1312 let grids: Vec<Grid> = sources
1313 .iter()
1314 .map(|(src, name)| {
1315 let (ops, n_in, res) = residual_from_source(src, name);
1316 route_straight_line(&ops, n_in, res)
1317 })
1318 .collect();
1319 let (r0, c0) = (grids[0].rows, grids[0].cols);
1320 for g in &grids {
1321 assert!(
1322 g.rows == r0 && g.cols == c0,
1323 "reconfig: all fabrics must place to the same array dimensions (use same-shape residuals)"
1324 );
1325 }
1326 let refs: Vec<&Grid> = grids.iter().collect();
1327 (0..grids.len())
1328 .map(|k| {
1329 let combined = time_mux_n_bounded(&refs, k as u64, width, cfg_width);
1330 let island = grid_to_bounded(&grids[k], width);
1331 let eq = lower_bool(&BoundedExpr::Eq(Box::new(combined), Box::new(island)))
1333 .expect("bitvec equality lowers to a boolean");
1334 prove_unsat(&ProofExpr::Not(Box::new(eq))) == UnsatOutcome::Refuted
1335 })
1336 .collect()
1337}
1338
1339pub fn pad_grid(g: &Grid, target_rows: usize, target_cols: usize) -> Grid {
1347 assert!(
1348 target_rows >= g.rows && target_cols >= g.cols,
1349 "pad_grid only grows an array (target {target_rows}x{target_cols} < {}x{})",
1350 g.rows,
1351 g.cols
1352 );
1353 let mut p = Grid::new(target_rows, target_cols, g.n_in);
1354 for r in 0..g.rows {
1355 for c in 0..g.cols {
1356 p.tiles[r][c] = g.tiles[r][c];
1357 }
1358 }
1359 p.out = g.out;
1360 p.extra_outs = g.extra_outs.clone();
1361 p
1362}
1363
1364pub fn grid_is_combinational(g: &Grid) -> bool {
1369 g.tiles.iter().flatten().all(|t| {
1370 !matches!(
1371 t.alu,
1372 Some(CellKind::Func(..)) | Some(CellKind::Mem(..)) | Some(CellKind::Simd(..))
1373 )
1374 })
1375}
1376
1377pub fn emit_time_multiplexed(name: &str, a: &Grid, b: &Grid) -> String {
1381 assert!(
1382 a.rows == b.rows && a.cols == b.cols,
1383 "time-mux requires equal grid dimensions"
1384 );
1385 let n_in = a.n_in.max(b.n_in);
1386 let inputs = (0..n_in)
1387 .map(|k| format!("input [63:0] in{k}"))
1388 .collect::<Vec<_>>()
1389 .join(", ");
1390 let inputs = if inputs.is_empty() {
1391 String::new()
1392 } else {
1393 format!("{inputs}, ")
1394 };
1395 let mut body = String::new();
1396 for r in 0..a.rows {
1397 for c in 0..a.cols {
1398 let nn = if r == 0 {
1399 "64'sd0".to_string()
1400 } else {
1401 format!("vs_{}_{c}", r - 1)
1402 };
1403 let ww = if c == 0 {
1404 "64'sd0".to_string()
1405 } else {
1406 format!("he_{r}_{}", c - 1)
1407 };
1408 for (tag, g) in [("a", a), ("b", b)] {
1409 let t = g.tiles[r][c];
1410 if let Some(k) = t.alu {
1411 body.push_str(&format!(
1412 " wire [63:0] alu{tag}_{r}_{c} = {};\n",
1413 cell_v(k, &nn, &ww)
1414 ));
1415 }
1416 let drv = |d: Drive| match d {
1417 Drive::Pass(s) => src2_v(s, &nn, &ww),
1418 Drive::Alu => format!("alu{tag}_{r}_{c}"),
1419 };
1420 body.push_str(&format!(" wire [63:0] vs{tag}_{r}_{c} = {};\n", drv(t.vs)));
1421 body.push_str(&format!(" wire [63:0] he{tag}_{r}_{c} = {};\n", drv(t.he)));
1422 }
1423 body.push_str(&format!(
1424 " wire [63:0] vs_{r}_{c} = ctx ? vsa_{r}_{c} : vsb_{r}_{c};\n"
1425 ));
1426 body.push_str(&format!(
1427 " wire [63:0] he_{r}_{c} = ctx ? hea_{r}_{c} : heb_{r}_{c};\n"
1428 ));
1429 }
1430 }
1431 let out_wire = |g: &Grid| match g.out.2 {
1432 Chan::Vs => format!("vs_{}_{}", g.out.0, g.out.1),
1433 Chan::He => format!("he_{}_{}", g.out.0, g.out.1),
1434 };
1435 format!(
1436 "// tmux_{name} — ONE {rows}×{cols} CGRA tile array running TWO fabrics by reconfiguration:\n\
1437 // every tile is muxed on `ctx` between fabric A (ctx=1) and fabric B (ctx=0). Certified by\n\
1438 // our SAT: ctx=1 ⇒ the array ≡ fabric A, ctx=0 ⇒ ≡ fabric B.\n\
1439 module tmux_{name}({inputs}input ctx, output [63:0] result);\n\
1440 {body} assign result = ctx ? {oa} : {ob};\n\
1441 endmodule\n",
1442 rows = a.rows,
1443 cols = a.cols,
1444 oa = out_wire(a),
1445 ob = out_wire(b),
1446 )
1447}
1448
1449pub fn emit_config_grid_verilog(name: &str, grids: &[&Grid], cfg_width: u32) -> String {
1456 assert!(!grids.is_empty(), "config grid needs at least one fabric");
1457 let rows = grids[0].rows;
1458 let cols = grids[0].cols;
1459 for g in grids {
1460 assert!(
1461 g.rows == rows && g.cols == cols,
1462 "config grid requires equal dims (pad_grid first)"
1463 );
1464 }
1465 let n_in = grids.iter().map(|g| g.n_in).max().unwrap();
1466 let inputs = (0..n_in)
1467 .map(|k| format!("input [63:0] in{k}"))
1468 .collect::<Vec<_>>()
1469 .join(", ");
1470 let inputs = if inputs.is_empty() {
1471 String::new()
1472 } else {
1473 format!("{inputs}, ")
1474 };
1475 let mux_n = |vals: &[String]| -> String {
1477 let mut acc = vals[vals.len() - 1].clone();
1478 for k in (0..vals.len() - 1).rev() {
1479 acc = format!("(cfg == {cfg_width}'d{k}) ? {} : ({acc})", vals[k]);
1480 }
1481 acc
1482 };
1483 let mut body = String::new();
1484 for r in 0..rows {
1485 for c in 0..cols {
1486 let nn = if r == 0 {
1487 "64'sd0".to_string()
1488 } else {
1489 format!("vs_{}_{c}", r - 1)
1490 };
1491 let ww = if c == 0 {
1492 "64'sd0".to_string()
1493 } else {
1494 format!("he_{r}_{}", c - 1)
1495 };
1496 let mut vsk = Vec::with_capacity(grids.len());
1497 let mut hek = Vec::with_capacity(grids.len());
1498 for (gi, g) in grids.iter().enumerate() {
1499 let t = g.tiles[r][c];
1500 if let Some(k) = t.alu {
1501 body.push_str(&format!(
1502 " wire [63:0] alu{gi}_{r}_{c} = {};\n",
1503 cell_v(k, &nn, &ww)
1504 ));
1505 }
1506 let drv = |d: Drive| match d {
1507 Drive::Pass(s) => src2_v(s, &nn, &ww),
1508 Drive::Alu => format!("alu{gi}_{r}_{c}"),
1509 };
1510 body.push_str(&format!(" wire [63:0] vs{gi}_{r}_{c} = {};\n", drv(t.vs)));
1511 body.push_str(&format!(" wire [63:0] he{gi}_{r}_{c} = {};\n", drv(t.he)));
1512 vsk.push(format!("vs{gi}_{r}_{c}"));
1513 hek.push(format!("he{gi}_{r}_{c}"));
1514 }
1515 body.push_str(&format!(" wire [63:0] vs_{r}_{c} = {};\n", mux_n(&vsk)));
1516 body.push_str(&format!(" wire [63:0] he_{r}_{c} = {};\n", mux_n(&hek)));
1517 }
1518 }
1519 let out_wire = |g: &Grid| match g.out.2 {
1520 Chan::Vs => format!("vs_{}_{}", g.out.0, g.out.1),
1521 Chan::He => format!("he_{}_{}", g.out.0, g.out.1),
1522 };
1523 let taps: Vec<String> = grids.iter().map(|g| out_wire(g)).collect();
1524 let provenance = if grids.iter().all(|g| grid_is_combinational(g)) {
1529 "// Certified by our SAT (certify_config_table): cfg==k ⇒ the array ≡ fabric k.\n"
1530 .to_string()
1531 } else {
1532 "// Proven by eval ≡ VM + the iverilog differential. NOT SAT-certified: a Func (division/float)\n\
1533 // tile has no bitblast lowering, so the SAT reconfig certifier refuses a table with one in it.\n"
1534 .to_string()
1535 };
1536 format!(
1537 "// config_{name} — ONE {rows}×{cols} CGRA array running {n} fabrics by RECONFIGURATION: every tile is\n\
1538 // muxed on the `cfg` word among all {n} fabrics over the shared switchbox channels; the result muxes\n\
1539 // the {n} output taps.\n\
1540 {provenance}\
1541 // Feed-forward south/east, so combinational — the answer settles in ONE clock.\n\
1542 module config_{name}({inputs}input [{w}:0] cfg, output [63:0] result);\n\
1543 {body} assign result = {res};\n\
1544 endmodule\n",
1545 n = grids.len(),
1546 w = cfg_width - 1,
1547 res = mux_n(&taps),
1548 )
1549}
1550
1551pub fn grid_area(g: &Grid) -> (usize, usize, usize) {
1554 let mut used = 0;
1555 for row in &g.tiles {
1556 for t in row {
1557 let live = t.alu.is_some()
1558 || !matches!(t.vs, Drive::Pass(Src2::Zero))
1559 || !matches!(t.he, Drive::Pass(Src2::Zero));
1560 if live {
1561 used += 1;
1562 }
1563 }
1564 }
1565 (g.rows, g.cols, used)
1566}
1567
1568pub fn route_leveled(cells: &[super::fabric_rtl::FabricOp], n_in: usize, result: usize) -> Grid {
1576 use super::fabric_rtl::Src as FSrc;
1577 let m = cells.len().max(1);
1578 let cols = m;
1579 let has_val = |c: &super::fabric_rtl::FabricOp| {
1580 matches!(c.a, FSrc::Val(_)) || matches!(c.b, FSrc::Val(_))
1581 };
1582 let mut compute_row = vec![0usize; cells.len()];
1584 let mut next = 1usize;
1585 for i in 0..cells.len() {
1586 if has_val(&cells[i]) {
1587 compute_row[i] = next + 1;
1588 next += 2;
1589 } else {
1590 compute_row[i] = 0;
1591 }
1592 }
1593 let rows = next.max(1);
1594 let mut g = Grid::new(rows, cols, n_in);
1595 let operand = |s: FSrc| -> (Src2, Option<usize>) {
1596 match s {
1597 FSrc::In(k) => (Src2::In(k), None),
1598 FSrc::Const(c) => (Src2::Const(c), None),
1599 FSrc::Val(j) => (Src2::Zero, Some(j)),
1600 }
1601 };
1602 for i in 0..cells.len() {
1603 let (ri, ci) = (compute_row[i], i);
1604 let (mut sa, ra) = operand(cells[i].a);
1605 let (mut sb, rb) = operand(cells[i].b);
1606 if ra.is_some() {
1607 sa = Src2::N;
1608 }
1609 if rb.is_some() {
1610 sb = Src2::W;
1611 }
1612 g.tiles[ri][ci].alu = Some(CellKind::Arith(cells[i].op, sa, sb));
1613 g.tiles[ri][ci].vs = Drive::Alu;
1614 route_north(&mut g, ra, ci, ri);
1617 route_west(&mut g, rb, ci, ri);
1618 }
1619 for i in 0..cells.len() {
1620 let (ri, ci) = (compute_row[i], i);
1621 for r in ri + 1..rows {
1622 if matches!(g.tiles[r][ci].vs, Drive::Pass(Src2::Zero)) {
1623 g.tiles[r][ci].vs = Drive::Pass(Src2::N);
1624 }
1625 }
1626 }
1627 g.out = (compute_row[result], result, Chan::Vs);
1628 g
1629}
1630
1631pub fn emit_grid_testbench(name: &str, n_in: usize, n: usize) -> String {
1634 let decls = (0..n_in)
1635 .map(|k| format!(" reg [63:0] in{k};"))
1636 .collect::<Vec<_>>()
1637 .join("\n");
1638 let ports = (0..n_in)
1639 .map(|k| format!(".in{k}(in{k})"))
1640 .collect::<Vec<_>>()
1641 .join(", ");
1642 let ports = if ports.is_empty() {
1643 String::new()
1644 } else {
1645 format!("{ports}, ")
1646 };
1647 let drive = (0..n_in)
1648 .map(|k| format!("in{k} = oin[i*{n_in}+{k}];"))
1649 .collect::<Vec<_>>()
1650 .join(" ");
1651 format!(
1652 r#"// tb_grid_{name} — the placed+routed CGRA tile array vs the RUNNING VM's output.
1653`timescale 1ns/1ps
1654module tb_grid_{name};
1655{decls}
1656 reg [63:0] oin [0:{ilast}];
1657 reg [63:0] oout [0:{olast}];
1658 wire [63:0] result;
1659 integer i, errors = 0;
1660 grid_{name} dut({ports}.result(result));
1661 initial begin
1662 $readmemh("grid_{name}_in.hex", oin);
1663 $readmemh("grid_{name}_out.hex", oout);
1664 for (i = 0; i < {n}; i = i + 1) begin
1665 {drive} #1;
1666 if (result !== oout[i]) begin errors = errors + 1; $display("FAIL case %0d: got %0d exp %0d", i, result, oout[i]); end
1667 end
1668 if (errors == 0) $display("PASS: {n} tile-array evaluations match the VM (placed+routed CGRA)");
1669 else $display("FAIL: %0d grid mismatches", errors);
1670 $finish;
1671 end
1672endmodule
1673"#,
1674 ilast = (n * n_in).saturating_sub(1),
1675 olast = n - 1,
1676 )
1677}
1678
1679#[cfg(test)]
1680#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/fabric_grid/tests.rs"]
1681mod tests;