1use super::fabric_rtl::{
27 emit_loop_fabric_verilog_pipelined, loop_fabric_from_source, poly_program, residual_from_source, AluOp, CmpKind,
28 DecisionFabric, FabricOp, LoopCell, Src,
29};
30
31pub fn is_wiring(op: &FabricOp) -> bool {
34 let konst = |s: &Src| matches!(s, Src::Const(_));
35 match op.op {
36 AluOp::Shl | AluOp::Shr | AluOp::UShr => konst(&op.b),
37 AluOp::And | AluOp::Or | AluOp::Xor => konst(&op.a) || konst(&op.b),
38 AluOp::Add | AluOp::Sub | AluOp::Mul => false,
39 }
40}
41
42pub fn op_delay(op: &FabricOp) -> f64 {
45 if is_wiring(op) {
46 return 0.0;
47 }
48 match op.op {
49 AluOp::Xor => 1.0,
50 AluOp::And | AluOp::Or => 0.6,
51 AluOp::Add | AluOp::Sub => 9.0,
52 AluOp::Mul => 40.0,
53 AluOp::Shl | AluOp::Shr | AluOp::UShr => 4.0,
54 }
55}
56
57struct Builder {
61 ops: Vec<FabricOp>,
62 shared: Option<std::collections::HashMap<FabricOp, Src>>,
63}
64
65impl Builder {
66 fn new() -> Builder {
67 Builder { ops: Vec::new(), shared: None }
68 }
69
70 fn sharing() -> Builder {
71 Builder { ops: Vec::new(), shared: Some(std::collections::HashMap::new()) }
72 }
73
74 fn push(&mut self, op: AluOp, a: Src, b: Src) -> Src {
75 let key = FabricOp { op, a, b };
76 if let Some(seen) = &self.shared {
77 let commutes = matches!(op, AluOp::Add | AluOp::Mul | AluOp::And | AluOp::Or | AluOp::Xor);
78 if let Some(&s) = seen.get(&key).or_else(|| commutes.then(|| seen.get(&FabricOp { op, a: b, b: a })).flatten()) {
79 return s;
80 }
81 }
82 self.ops.push(key);
83 let s = Src::Val(self.ops.len() - 1);
84 if let Some(seen) = &mut self.shared {
85 seen.insert(key, s);
86 }
87 s
88 }
89
90 fn emit(&mut self, op: AluOp, a: Src, b: Src) -> Src {
93 use AluOp::*;
94 match (op, a, b) {
95 (And | Or | Xor | Add | Sub | Mul | Shl, Src::Const(x), Src::Const(y)) => Src::Const(op.eval(x, y)),
96 (And, Src::Const(0), _) | (And, _, Src::Const(0)) => Src::Const(0),
97 (And, Src::Const(-1), s) | (And, s, Src::Const(-1)) => s,
98 (Or | Xor, Src::Const(0), s) | (Or | Xor, s, Src::Const(0)) => s,
99 (Shl | Shr | UShr, s, Src::Const(0)) => s,
100 _ => self.push(op, a, b),
101 }
102 }
103}
104
105pub fn csd_digits(c: i64, width: u32) -> Vec<(u32, i64)> {
108 let modulus = 1u128 << width;
109 let mut v = (c as u64 as u128) % modulus;
110 let mut digits = Vec::new();
111 let mut k = 0u32;
112 while v != 0 && k < width {
113 if v & 1 == 1 {
114 let d: i64 = if k + 1 == width || v & 3 == 1 { 1 } else { -1 };
115 digits.push((k, d));
116 v = if d == 1 { v - 1 } else { v + 1 };
117 }
118 v >>= 1;
119 k += 1;
120 }
121 digits
122}
123
124fn width_mask(width: u32) -> i64 {
125 if width == 64 {
126 -1
127 } else {
128 ((1u64 << width) - 1) as i64
129 }
130}
131
132#[derive(Default)]
134struct Rows {
135 words: Vec<(Src, bool)>,
136 constant: i64,
137}
138
139impl Rows {
140 fn push(&mut self, s: Src, negated: bool) {
141 match s {
142 Src::Const(c) if negated => self.constant = self.constant.wrapping_sub(c),
143 Src::Const(c) => self.constant = self.constant.wrapping_add(c),
144 s => self.words.push((s, negated)),
145 }
146 }
147}
148
149struct Lowering<'a> {
150 ops: &'a [FabricOp],
151 width: u32,
152 final_add: FinalAdd,
153 resolves: Vec<bool>,
154 folds: Vec<bool>,
156 lowered: Vec<Option<Src>>,
157 out: Builder,
158}
159
160fn is_additive(op: &FabricOp) -> bool {
161 matches!(op.op, AluOp::Add | AluOp::Sub | AluOp::Mul)
162}
163
164impl Lowering<'_> {
165 fn leaf(&self, s: Src) -> Src {
166 match s {
167 Src::Val(k) => self.lowered[k].expect("a value is lowered before its readers"),
168 other => other,
169 }
170 }
171
172 fn expand(&mut self, s: Src, negated: bool, rows: &mut Rows) {
173 match s {
174 Src::Val(k) if is_additive(&self.ops[k]) && (!self.resolves[k] || self.folds[k]) => {
175 self.expand_op(k, negated, rows)
176 }
177 other => {
178 let leaf = self.leaf(other);
179 rows.push(leaf, negated);
180 }
181 }
182 }
183
184 fn expand_op(&mut self, k: usize, negated: bool, rows: &mut Rows) {
185 let op = self.ops[k];
186 match op.op {
187 AluOp::Add => {
188 self.expand(op.a, negated, rows);
189 self.expand(op.b, negated, rows);
190 }
191 AluOp::Sub => {
192 self.expand(op.a, negated, rows);
193 self.expand(op.b, !negated, rows);
194 }
195 AluOp::Mul => self.multiply_rows(op.a, op.b, negated, rows),
196 _ => unreachable!("only additive ops expand into rows"),
197 }
198 }
199
200 fn multiply_rows(&mut self, a: Src, b: Src, negated: bool, rows: &mut Rows) {
203 let w = self.width;
204 match (self.leaf(a), self.leaf(b)) {
205 (Src::Const(x), Src::Const(y)) => rows.push(Src::Const(x.wrapping_mul(y)), negated),
206 (s, Src::Const(c)) | (Src::Const(c), s) => {
207 for (k, d) in csd_digits(c, w) {
208 let row = self.out.emit(AluOp::Shl, s, Src::Const(k as i64));
209 rows.push(row, negated != (d < 0));
210 }
211 }
212 (x, y) => {
213 for i in 0..w {
214 let shifted = self.out.emit(AluOp::Shl, x, Src::Const(i as i64));
215 let top = self.out.emit(AluOp::Shl, y, Src::Const((w - 1 - i) as i64));
216 let mask = self.out.emit(AluOp::Shr, top, Src::Const((w - 1) as i64));
217 let row = self.out.emit(AluOp::And, shifted, mask);
218 rows.push(row, negated);
219 }
220 }
221 }
222 }
223
224 fn compress(&mut self, a: Src, b: Src, c: Src) -> (Src, Src) {
226 let ab = self.out.emit(AluOp::Xor, a, b);
227 let sum = self.out.emit(AluOp::Xor, ab, c);
228 let both = self.out.emit(AluOp::And, a, b);
229 let carried = self.out.emit(AluOp::And, c, ab);
230 let majority = self.out.emit(AluOp::Or, both, carried);
231 let carry = self.out.emit(AluOp::Shl, majority, Src::Const(1));
232 (sum, carry)
233 }
234
235 fn prefix_add(&mut self, a: Src, b: Src) -> Src {
238 if let (Src::Const(x), Src::Const(y)) = (a, b) {
239 return Src::Const(x.wrapping_add(y));
240 }
241 let mut generate = self.out.emit(AluOp::And, a, b);
242 let half_sum = self.out.emit(AluOp::Xor, a, b);
243 let mut propagate = half_sum;
244 let mut span = 1u32;
245 while span < self.width {
246 let lower = self.out.emit(AluOp::Shl, generate, Src::Const(span as i64));
247 let through = self.out.emit(AluOp::And, propagate, lower);
248 generate = self.out.emit(AluOp::Or, generate, through);
249 if 2 * span < self.width {
250 let lower = self.out.emit(AluOp::Shl, propagate, Src::Const(span as i64));
251 propagate = self.out.emit(AluOp::And, propagate, lower);
252 }
253 span *= 2;
254 }
255 let carries = self.out.emit(AluOp::Shl, generate, Src::Const(1));
256 self.out.emit(AluOp::Xor, half_sum, carries)
257 }
258
259 fn resolve(&mut self, rows: Rows) -> Src {
260 let mut constant = rows.constant;
261 let mut words: Vec<Src> = Vec::with_capacity(rows.words.len() + 1);
262 for (s, negated) in rows.words {
263 if negated {
264 words.push(self.out.emit(AluOp::Xor, s, Src::Const(-1)));
265 constant = constant.wrapping_add(1);
266 } else {
267 words.push(s);
268 }
269 }
270 if constant & width_mask(self.width) != 0 {
271 words.push(Src::Const(constant));
272 }
273 if words.is_empty() {
274 return Src::Const(0);
275 }
276 while words.len() > 2 {
277 let mut next = Vec::with_capacity(words.len() * 2 / 3 + 2);
278 let mut triples = words.chunks_exact(3);
279 for t in &mut triples {
280 let (sum, carry) = self.compress(t[0], t[1], t[2]);
281 next.push(sum);
282 next.push(carry);
283 }
284 next.extend_from_slice(triples.remainder());
285 words = next;
286 }
287 if words.len() == 1 {
288 words[0]
289 } else if self.final_add == FinalAdd::Native {
290 self.out.emit(AluOp::Add, words[0], words[1])
291 } else {
292 self.prefix_add(words[0], words[1])
293 }
294 }
295}
296
297#[derive(Clone, Copy, Debug, PartialEq, Eq)]
299pub enum SumSharing {
300 Resolve,
302 Fold,
307}
308
309pub const FOLD_ROW_LIMIT: usize = 8;
311
312#[derive(Clone, Copy, Debug, PartialEq, Eq)]
314pub enum FinalAdd {
315 PrefixTree,
318 Native,
321}
322
323pub fn carry_save_lower(ops: &[FabricOp], result: usize, width: u32) -> (Vec<FabricOp>, usize) {
330 carry_save_lower_with(ops, result, width, SumSharing::Resolve, FinalAdd::PrefixTree)
331}
332
333pub fn carry_save_lower_with(
339 ops: &[FabricOp],
340 result: usize,
341 width: u32,
342 sharing: SumSharing,
343 final_add: FinalAdd,
344) -> (Vec<FabricOp>, usize) {
345 assert!((2..=64).contains(&width), "the datapath width is 2..=64 bits, not {width}");
346 assert!(result < ops.len(), "the result names an op of the residual");
347 let n = ops.len();
348 let live = needed_by(ops, result);
349 let mut readers = vec![0usize; n];
350 let mut resolves = vec![false; n];
351 resolves[result] = true;
352 for op in ops.iter().zip(&live).filter(|(_, l)| **l).map(|(op, _)| op) {
353 for s in [op.a, op.b] {
354 if let Src::Val(k) = s {
355 readers[k] += 1;
356 if !matches!(op.op, AluOp::Add | AluOp::Sub) {
357 resolves[k] = true;
358 }
359 }
360 }
361 }
362 let mut folds = vec![false; n];
363 match sharing {
364 SumSharing::Resolve => {
365 for k in 0..n {
366 resolves[k] |= readers[k] > 1;
367 }
368 }
369 SumSharing::Fold => {
370 let mut words = vec![1usize; n];
373 for k in 0..n {
374 let term = |s: Src, words: &[usize], resolves: &[bool], folds: &[bool]| match s {
375 Src::Const(_) => 0,
376 Src::In(_) => 1,
377 Src::Val(j) if is_additive(&ops[j]) && (!resolves[j] || folds[j]) => words[j],
378 Src::Val(_) => 1,
379 };
380 match ops[k].op {
381 AluOp::Add | AluOp::Sub => {
382 words[k] = term(ops[k].a, &words, &resolves, &folds) + term(ops[k].b, &words, &resolves, &folds);
383 folds[k] = words[k] <= FOLD_ROW_LIMIT;
384 resolves[k] |= !folds[k] && readers[k] > 1;
385 }
386 AluOp::Mul => {
387 resolves[k] |= readers[k] > 1;
388 words[k] = width as usize;
389 }
390 _ => {}
391 }
392 }
393 }
394 }
395 let mut lo = Lowering {
396 ops,
397 width,
398 final_add,
399 resolves,
400 folds,
401 lowered: vec![None; n],
402 out: match sharing {
403 SumSharing::Resolve => Builder::new(),
404 SumSharing::Fold => Builder::sharing(),
405 },
406 };
407 for i in 0..n {
408 if !live[i] {
409 continue;
410 }
411 let op = ops[i];
412 if is_additive(&op) {
413 if !lo.resolves[i] {
414 continue;
415 }
416 let mut rows = Rows::default();
417 lo.expand_op(i, false, &mut rows);
418 let s = lo.resolve(rows);
419 lo.lowered[i] = Some(s);
420 } else {
421 let (a, b) = (lo.leaf(op.a), lo.leaf(op.b));
422 let s = lo.out.emit(op.op, a, b);
423 lo.lowered[i] = Some(s);
424 }
425 }
426 let result = match lo.lowered[result].expect("the result is lowered") {
427 Src::Val(k) => k,
428 other => {
429 lo.out.push(AluOp::Or, other, Src::Const(0));
430 lo.out.ops.len() - 1
431 }
432 };
433 (lo.out.ops, result)
434}
435
436pub const DECISION_CHAIN_BUDGET: f64 = 9.6;
443
444pub const DECISION_CELLS_BUDGET: f64 = 30.0;
453
454pub fn decision_ops(df: &DecisionFabric) -> Result<(Vec<FabricOp>, usize), String> {
460 let mut out = Builder::sharing();
461 let mut vals: Vec<Src> = Vec::with_capacity(df.cells.len());
462 let at = |s: Src, vals: &[Src]| match s {
463 Src::Val(k) => vals[k],
464 other => other,
465 };
466 for (i, cell) in df.cells.iter().enumerate() {
467 let v = match *cell {
468 LoopCell::Arith { op, a, b } => out.emit(op, at(a, &vals), at(b, &vals)),
469 LoopCell::Select { cmp, ca, cb, t, e } => {
470 let mask = compare_mask(&mut out, cmp, at(ca, &vals), at(cb, &vals));
471 let (t, e) = (at(t, &vals), at(e, &vals));
472 let differ = out.emit(AluOp::Xor, t, e);
473 let taken = out.emit(AluOp::And, differ, mask);
474 out.emit(AluOp::Xor, e, taken)
475 }
476 ref other => return Err(format!("cell {i} ({other:?}) has no combinational form in a pipeline")),
477 };
478 vals.push(v);
479 }
480 let result = match at(df.result, &vals) {
481 Src::Val(k) => k,
482 other => {
483 out.push(AluOp::Or, other, Src::Const(0));
484 out.ops.len() - 1
485 }
486 };
487 Ok((out.ops, result))
488}
489
490fn compare_mask(out: &mut Builder, cmp: CmpKind, x: Src, y: Src) -> Src {
496 fn less(out: &mut Builder, a: Src, b: Src) -> Src {
497 let d = out.emit(AluOp::Sub, a, b);
498 let signs = out.emit(AluOp::Xor, a, b);
499 let moved = out.emit(AluOp::Xor, a, d);
500 let overflow = out.emit(AluOp::And, signs, moved);
501 let sign = out.emit(AluOp::Xor, d, overflow);
502 out.emit(AluOp::Shr, sign, Src::Const(63))
503 }
504 fn differ(out: &mut Builder, a: Src, b: Src) -> Src {
505 let mut any = out.emit(AluOp::Xor, a, b);
506 for k in [1, 2, 4, 8, 16, 32] {
507 let up = out.emit(AluOp::Shl, any, Src::Const(k));
508 any = out.emit(AluOp::Or, any, up);
509 }
510 out.emit(AluOp::Shr, any, Src::Const(63))
511 }
512 let mask = match cmp {
513 CmpKind::Lt | CmpKind::Ge => less(out, x, y),
514 CmpKind::Gt | CmpKind::Le => less(out, y, x),
515 CmpKind::Ne | CmpKind::Eq => differ(out, x, y),
516 };
517 match cmp {
518 CmpKind::Ge | CmpKind::Le | CmpKind::Eq => out.emit(AluOp::Xor, mask, Src::Const(-1)),
519 CmpKind::Lt | CmpKind::Gt | CmpKind::Ne => mask,
520 }
521}
522
523#[derive(Clone, Copy, Debug, PartialEq, Eq)]
525pub enum Inputs {
526 Registered,
528 Ports,
531}
532
533#[derive(Clone, Debug, PartialEq)]
535pub struct PipelineSchedule {
536 pub stage: Vec<usize>,
538 pub result: usize,
539 pub budget: f64,
541 pub inputs: Inputs,
543}
544
545impl PipelineSchedule {
546 pub fn stages(&self) -> usize {
548 self.stage[self.result] + 1
549 }
550
551 pub fn latency(&self) -> usize {
554 self.stage[self.result] + 1 + usize::from(self.inputs == Inputs::Registered)
555 }
556
557 pub fn reading(self, inputs: Inputs) -> PipelineSchedule {
559 PipelineSchedule { inputs, ..self }
560 }
561
562 pub fn stage_depth(&self, ops: &[FabricOp]) -> f64 {
566 let needed = needed_by(ops, self.result);
567 stage_arrivals(ops, &self.stage)
568 .into_iter()
569 .zip(needed)
570 .filter(|(_, n)| *n)
571 .map(|(a, _)| a)
572 .fold(0.0, f64::max)
573 }
574
575 pub fn check(&self, ops: &[FabricOp]) -> Result<(), String> {
577 if self.stage.len() != ops.len() {
578 return Err(format!("{} stages for {} ops", self.stage.len(), ops.len()));
579 }
580 for (i, op) in ops.iter().enumerate() {
581 let mut latest = 0;
582 for s in [op.a, op.b] {
583 if let Src::Val(k) = s {
584 if k >= i {
585 return Err(format!("op {i} reads v{k}, which is not computed before it"));
586 }
587 if self.stage[k] > self.stage[i] {
588 return Err(format!(
589 "op {i} in stage {} reads v{k} from the later stage {}",
590 self.stage[i], self.stage[k]
591 ));
592 }
593 latest = latest.max(self.stage[k]);
594 }
595 }
596 if is_wiring(op) && self.stage[i] != latest {
597 return Err(format!(
598 "wiring op {i} sits in stage {} rather than its operands' stage {latest}",
599 self.stage[i]
600 ));
601 }
602 }
603 Ok(())
604 }
605}
606
607fn needed_by(ops: &[FabricOp], result: usize) -> Vec<bool> {
609 let mut needed = vec![false; ops.len()];
610 needed[result] = true;
611 for i in (0..ops.len()).rev() {
612 if needed[i] {
613 for s in [ops[i].a, ops[i].b] {
614 if let Src::Val(k) = s {
615 needed[k] = true;
616 }
617 }
618 }
619 }
620 needed
621}
622
623fn stage_arrivals(ops: &[FabricOp], stage: &[usize]) -> Vec<f64> {
624 let mut arrival = vec![0.0f64; ops.len()];
625 for (i, op) in ops.iter().enumerate() {
626 let start = [op.a, op.b]
627 .into_iter()
628 .filter_map(|s| match s {
629 Src::Val(k) if stage[k] == stage[i] => Some(arrival[k]),
630 _ => None,
631 })
632 .fold(0.0, f64::max);
633 arrival[i] = start + op_delay(op);
634 }
635 arrival
636}
637
638pub fn known_bits(ops: &[FabricOp]) -> Vec<(u64, u64)> {
642 let mut known: Vec<(u64, u64)> = Vec::with_capacity(ops.len());
643 for op in ops {
644 let of = |s: Src, known: &[(u64, u64)]| match s {
645 Src::Const(c) => (u64::MAX, c as u64),
646 Src::In(_) => (0, 0),
647 Src::Val(k) => known[k],
648 };
649 let ((ma, va), (mb, vb)) = (of(op.a, &known), of(op.b, &known));
650 let low_zeros = |m: u64, v: u64| (!(!m | v)).trailing_ones();
651 let (mask, value) = match (op.op, op.b) {
652 (AluOp::And, _) => {
653 let zeros = (ma & !va) | (mb & !vb);
654 let mask = (ma & mb) | zeros;
655 (mask, va & vb & mask)
656 }
657 (AluOp::Or, _) => {
658 let ones = (ma & va) | (mb & vb);
659 let mask = (ma & mb) | ones;
660 (mask, (va | vb) & mask)
661 }
662 (AluOp::Xor, _) => (ma & mb, (va ^ vb) & ma & mb),
663 (AluOp::Shl, Src::Const(k)) => {
664 let k = (k & 63) as u32;
665 let low = if k == 0 { 0 } else { u64::MAX >> (64 - k) };
666 (ma.wrapping_shl(k) | low, va.wrapping_shl(k))
667 }
668 (AluOp::UShr, Src::Const(k)) => {
669 let k = (k & 63) as u32;
670 let high = if k == 0 { 0 } else { u64::MAX << (64 - k) };
671 (ma.wrapping_shr(k) | high, va.wrapping_shr(k))
672 }
673 (AluOp::Shr, Src::Const(k)) => {
674 let k = (k & 63) as u32;
675 let sign_known = ma >> 63 == 1;
676 let high = if k == 0 || !sign_known { 0 } else { u64::MAX << (64 - k) };
677 let mask = ma.wrapping_shr(k) | high;
678 (mask, ((va as i64).wrapping_shr(k) as u64) & mask)
679 }
680 (AluOp::Add | AluOp::Sub, _) => {
681 let t = low_zeros(ma, va).min(low_zeros(mb, vb));
682 (if t == 0 { 0 } else { u64::MAX >> (64 - t) }, 0)
683 }
684 (AluOp::Mul, _) => {
685 let t = (low_zeros(ma, va) + low_zeros(mb, vb)).min(64);
686 (if t == 0 { 0 } else { u64::MAX >> (64 - t.min(64)) }, 0)
687 }
688 _ => (0, 0),
689 };
690 known.push((mask, value & mask));
691 }
692 known
693}
694
695fn live_bits(known: (u64, u64)) -> u64 {
696 64 - u64::from(known.0.count_ones())
697}
698
699struct StageProblem {
703 needed: Vec<bool>,
705 reads: Vec<Vec<Held>>,
707 result_reads: Vec<Held>,
709 constraints: Vec<(usize, usize, usize)>,
711 earliest: Vec<usize>,
712 latest: Vec<usize>,
713 last: usize,
715}
716
717fn sources(ops: &[FabricOp], s: Src, memo: &mut Vec<Option<Vec<Held>>>) -> Vec<Held> {
718 match s {
719 Src::Const(_) => Vec::new(),
720 Src::In(k) => vec![Held::Input(k)],
721 Src::Val(k) if is_wiring(&ops[k]) => {
722 if memo[k].is_none() {
723 let mut v = sources(ops, ops[k].a, memo);
724 v.extend(sources(ops, ops[k].b, memo));
725 v.sort();
726 v.dedup();
727 memo[k] = Some(v);
728 }
729 memo[k].clone().expect("memoized")
730 }
731 Src::Val(k) => vec![Held::Value(k)],
732 }
733}
734
735impl StageProblem {
736 fn new(ops: &[FabricOp], result: usize, budget: f64) -> StageProblem {
737 assert!(budget > 0.0, "a stage budget is positive");
738 assert!(result < ops.len(), "the result names an op of the residual");
739 let n = ops.len();
740 let needed = needed_by(ops, result);
741 let computed = |i: usize| needed[i] && !is_wiring(&ops[i]);
742 let mut memo = vec![None; n];
743 let mut reads = vec![Vec::new(); n];
744 for i in (0..n).filter(|&i| computed(i)) {
745 let mut v = sources(ops, ops[i].a, &mut memo);
746 v.extend(sources(ops, ops[i].b, &mut memo));
747 v.sort();
748 v.dedup();
749 reads[i] = v;
750 }
751 let result_reads = sources(ops, Src::Val(result), &mut memo);
752 let mut readers = vec![Vec::new(); n];
753 for i in (0..n).filter(|&i| computed(i)) {
754 for h in &reads[i] {
755 if let Held::Value(k) = h {
756 readers[*k].push(i);
757 }
758 }
759 }
760
761 let mut constraints = Vec::new();
762 for i in (0..n).filter(|&i| computed(i)) {
763 for h in &reads[i] {
764 if let Held::Value(k) = h {
765 constraints.push((*k, i, 0));
766 }
767 }
768 }
769 let mut reach = vec![f64::NAN; n];
770 for u in (0..n).filter(|&u| computed(u)) {
771 let mut frontier = std::collections::BinaryHeap::new();
772 let mut touched = vec![u];
773 reach[u] = op_delay(&ops[u]);
774 if reach[u] <= budget {
775 frontier.extend(readers[u].iter().map(|&r| std::cmp::Reverse(r)));
776 } else {
777 constraints.extend(readers[u].iter().map(|&r| (u, r, 1)));
778 }
779 while let Some(std::cmp::Reverse(v)) = frontier.pop() {
780 if !reach[v].is_nan() {
781 continue;
782 }
783 let start = reads[v]
784 .iter()
785 .filter_map(|h| match h {
786 Held::Value(p) if !reach[*p].is_nan() => Some(reach[*p]),
787 _ => None,
788 })
789 .fold(0.0, f64::max);
790 reach[v] = start + op_delay(&ops[v]);
791 touched.push(v);
792 if reach[v] > budget {
793 constraints.push((u, v, 1));
794 } else {
795 frontier.extend(readers[v].iter().map(|&r| std::cmp::Reverse(r)));
796 }
797 }
798 for t in touched {
799 reach[t] = f64::NAN;
800 }
801 }
802 constraints.sort();
803 constraints.dedup();
804 let mut strongest: std::collections::BTreeMap<(usize, usize), usize> = Default::default();
805 for &(x, y, w) in &constraints {
806 let e = strongest.entry((x, y)).or_insert(0);
807 *e = (*e).max(w);
808 }
809 let constraints: Vec<(usize, usize, usize)> = strongest.into_iter().map(|((x, y), w)| (x, y, w)).collect();
810
811 let mut earliest = vec![0usize; n];
812 let mut incoming: Vec<Vec<(usize, usize)>> = vec![Vec::new(); n];
813 let mut outgoing: Vec<Vec<(usize, usize)>> = vec![Vec::new(); n];
814 for &(x, y, w) in &constraints {
815 incoming[y].push((x, w));
816 outgoing[x].push((y, w));
817 }
818 for i in (0..n).filter(|&i| computed(i)) {
819 earliest[i] = incoming[i].iter().map(|&(x, w)| earliest[x] + w).max().unwrap_or(0);
820 }
821 let last = result_reads
822 .iter()
823 .map(|h| match h {
824 Held::Value(k) => earliest[*k],
825 Held::Input(_) => 0,
826 })
827 .max()
828 .unwrap_or(0);
829 let mut latest = vec![last; n];
830 for i in (0..n).rev().filter(|&i| computed(i)) {
831 for &(y, w) in &outgoing[i] {
832 latest[i] = latest[i].min(latest[y] - w);
833 }
834 debug_assert!(earliest[i] <= latest[i], "op {i}: earliest {} after latest {}", earliest[i], latest[i]);
835 }
836 StageProblem {
837 needed,
838 reads,
839 result_reads,
840 constraints,
841 earliest,
842 latest,
843 last,
844 }
845 }
846
847 fn schedule(&self, ops: &[FabricOp], result: usize, budget: f64, computed_stage: &[usize]) -> PipelineSchedule {
850 let mut stage = vec![0usize; ops.len()];
851 for (i, op) in ops.iter().enumerate() {
852 stage[i] = if self.needed[i] && !is_wiring(op) {
853 computed_stage[i]
854 } else {
855 [op.a, op.b]
856 .into_iter()
857 .filter_map(|s| match s {
858 Src::Val(k) => Some(stage[k]),
859 _ => None,
860 })
861 .max()
862 .unwrap_or(0)
863 };
864 }
865 PipelineSchedule {
866 stage,
867 result,
868 budget,
869 inputs: Inputs::Registered,
870 }
871 }
872}
873
874pub fn earliest_schedule(ops: &[FabricOp], result: usize, budget: f64) -> PipelineSchedule {
876 let p = StageProblem::new(ops, result, budget);
877 p.schedule(ops, result, budget, &p.earliest)
878}
879
880pub fn latest_schedule(ops: &[FabricOp], result: usize, budget: f64) -> PipelineSchedule {
882 let p = StageProblem::new(ops, result, budget);
883 p.schedule(ops, result, budget, &p.latest)
884}
885
886pub fn schedule_pipeline(ops: &[FabricOp], result: usize, budget: f64) -> PipelineSchedule {
896 let p = StageProblem::new(ops, result, budget);
897 let n = ops.len();
898 let known = known_bits(ops);
899 let bits = |h: Held| -> i64 {
900 match h {
901 Held::Input(_) => 64,
902 Held::Value(k) => live_bits(known[k]) as i64,
903 }
904 };
905
906 let mut lo: Vec<usize> = Vec::new();
908 let mut hi: Vec<usize> = Vec::new();
909 let mut weight: Vec<i64> = Vec::new();
910 let mut var_of_op = vec![usize::MAX; n];
911 for i in 0..n {
912 if p.needed[i] && !is_wiring(&ops[i]) {
913 var_of_op[i] = lo.len();
914 lo.push(p.earliest[i]);
915 hi.push(p.latest[i]);
916 weight.push(0);
917 }
918 }
919 let mut readers_of: std::collections::BTreeMap<Held, Vec<usize>> = Default::default();
920 for i in 0..n {
921 if var_of_op[i] != usize::MAX {
922 for &h in &p.reads[i] {
923 readers_of.entry(h).or_default().push(var_of_op[i]);
924 }
925 }
926 }
927 for &h in &p.result_reads {
928 readers_of.entry(h).or_default();
929 }
930 let mut edges: Vec<(usize, usize, usize)> = p
931 .constraints
932 .iter()
933 .map(|&(x, y, w)| (var_of_op[x], var_of_op[y], w))
934 .collect();
935 for (&h, readers) in &readers_of {
936 let reads_result = p.result_reads.contains(&h);
937 let mlo = readers
938 .iter()
939 .map(|&r| lo[r])
940 .chain(reads_result.then_some(p.last))
941 .chain(match h {
942 Held::Value(k) => Some(p.earliest[k]),
943 Held::Input(_) => Some(0),
944 })
945 .max()
946 .unwrap_or(0);
947 let mhi = readers
948 .iter()
949 .map(|&r| hi[r])
950 .chain(reads_result.then_some(p.last))
951 .max()
952 .unwrap_or(mlo)
953 .max(mlo);
954 let m = lo.len();
955 lo.push(mlo);
956 hi.push(mhi);
957 weight.push(bits(h));
958 if let Held::Value(k) = h {
959 weight[var_of_op[k]] -= bits(h);
960 }
961 edges.extend(readers.iter().map(|&r| (r, m, 0)));
962 }
963
964 let mut level_base = Vec::with_capacity(lo.len());
966 let mut nodes = 0usize;
967 for x in 0..lo.len() {
968 level_base.push(nodes);
969 nodes += hi[x] - lo[x];
970 }
971 let node = |x: usize, k: usize| level_base[x] + (k - lo[x] - 1);
972 let (source, sink) = (nodes, nodes + 1);
973 let positive: i64 = (0..lo.len())
974 .map(|x| (-weight[x]).max(0) * (hi[x] - lo[x]) as i64)
975 .sum();
976 let infinite = positive + 1;
977 let mut flow = MaxFlow::new(nodes + 2);
978 for x in 0..lo.len() {
979 for k in lo[x] + 1..=hi[x] {
980 let gain = -weight[x];
981 if gain > 0 {
982 flow.add(source, node(x, k), gain);
983 } else if gain < 0 {
984 flow.add(node(x, k), sink, -gain);
985 }
986 if k > lo[x] + 1 {
987 flow.add(node(x, k), node(x, k - 1), infinite);
988 }
989 }
990 }
991 for &(x, y, w) in &edges {
992 for k in lo[x] + 1..=hi[x] {
993 let j = k + w;
994 if j <= lo[y] {
995 continue;
996 }
997 assert!(j <= hi[y], "stage bounds admit a constraint violation: {x} → {y} (+{w}) at level {k}");
998 flow.add(node(x, k), node(y, j), infinite);
999 }
1000 }
1001 flow.max_flow(source, sink);
1002 let chosen = flow.reachable(source);
1003 let mut computed_stage = vec![0usize; n];
1004 for i in 0..n {
1005 let x = var_of_op[i];
1006 if x != usize::MAX {
1007 computed_stage[i] = lo[x] + (lo[x] + 1..=hi[x]).filter(|&k| chosen[node(x, k)]).count();
1008 }
1009 }
1010 p.schedule(ops, result, budget, &computed_stage)
1011}
1012
1013struct MaxFlow {
1015 head: Vec<usize>,
1016 next: Vec<usize>,
1017 to: Vec<usize>,
1018 cap: Vec<i64>,
1019}
1020
1021impl MaxFlow {
1022 const NONE: usize = usize::MAX;
1023
1024 fn new(n: usize) -> MaxFlow {
1025 MaxFlow {
1026 head: vec![Self::NONE; n],
1027 next: Vec::new(),
1028 to: Vec::new(),
1029 cap: Vec::new(),
1030 }
1031 }
1032
1033 fn add(&mut self, u: usize, v: usize, c: i64) {
1034 for (a, b, c) in [(u, v, c), (v, u, 0)] {
1035 self.next.push(self.head[a]);
1036 self.to.push(b);
1037 self.cap.push(c);
1038 self.head[a] = self.to.len() - 1;
1039 }
1040 }
1041
1042 fn levels(&self, s: usize) -> Vec<usize> {
1043 let mut level = vec![usize::MAX; self.head.len()];
1044 let mut queue = std::collections::VecDeque::from([s]);
1045 level[s] = 0;
1046 while let Some(u) = queue.pop_front() {
1047 let mut e = self.head[u];
1048 while e != Self::NONE {
1049 if self.cap[e] > 0 && level[self.to[e]] == usize::MAX {
1050 level[self.to[e]] = level[u] + 1;
1051 queue.push_back(self.to[e]);
1052 }
1053 e = self.next[e];
1054 }
1055 }
1056 level
1057 }
1058
1059 fn max_flow(&mut self, s: usize, t: usize) -> i64 {
1060 let mut total = 0;
1061 loop {
1062 let level = self.levels(s);
1063 if level[t] == usize::MAX {
1064 return total;
1065 }
1066 let mut cursor = self.head.clone();
1067 loop {
1068 let pushed = self.augment(s, t, i64::MAX, &level, &mut cursor);
1069 if pushed == 0 {
1070 break;
1071 }
1072 total += pushed;
1073 }
1074 }
1075 }
1076
1077 fn augment(&mut self, s: usize, t: usize, limit: i64, level: &[usize], cursor: &mut [usize]) -> i64 {
1079 let mut path: Vec<usize> = Vec::new();
1080 let mut u = s;
1081 loop {
1082 if u == t {
1083 let pushed = path.iter().map(|&e| self.cap[e]).fold(limit, i64::min);
1084 for &e in &path {
1085 self.cap[e] -= pushed;
1086 self.cap[e ^ 1] += pushed;
1087 }
1088 return pushed;
1089 }
1090 let mut advanced = false;
1091 while cursor[u] != Self::NONE {
1092 let e = cursor[u];
1093 let v = self.to[e];
1094 if self.cap[e] > 0 && level[v] == level[u] + 1 {
1095 path.push(e);
1096 u = v;
1097 advanced = true;
1098 break;
1099 }
1100 cursor[u] = self.next[e];
1101 }
1102 if !advanced {
1103 if u == s {
1104 return 0;
1105 }
1106 let e = path.pop().expect("a non-source node is entered by an edge");
1107 let back = self.to[e ^ 1];
1108 cursor[back] = self.next[cursor[back]];
1109 u = back;
1110 }
1111 }
1112 }
1113
1114 fn reachable(&self, s: usize) -> Vec<bool> {
1115 let mut seen = vec![false; self.head.len()];
1116 let mut stack = vec![s];
1117 seen[s] = true;
1118 while let Some(u) = stack.pop() {
1119 let mut e = self.head[u];
1120 while e != Self::NONE {
1121 if self.cap[e] > 0 && !seen[self.to[e]] {
1122 seen[self.to[e]] = true;
1123 stack.push(self.to[e]);
1124 }
1125 e = self.next[e];
1126 }
1127 }
1128 seen
1129 }
1130}
1131
1132#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1134enum Held {
1135 Input(usize),
1136 Value(usize),
1137}
1138
1139struct Registers {
1142 depth: std::collections::BTreeMap<Held, usize>,
1143 needed: Vec<bool>,
1144}
1145
1146fn registers(ops: &[FabricOp], sched: &PipelineSchedule) -> Registers {
1147 let n = ops.len();
1148 let needed = needed_by(ops, sched.result);
1149 let mut depth = std::collections::BTreeMap::new();
1150 fn want(
1151 ops: &[FabricOp],
1152 stage: &[usize],
1153 s: Src,
1154 at: usize,
1155 depth: &mut std::collections::BTreeMap<Held, usize>,
1156 ) {
1157 let (held, born) = match s {
1158 Src::Const(_) => return,
1159 Src::Val(k) if is_wiring(&ops[k]) => {
1160 want(ops, stage, ops[k].a, at, depth);
1161 want(ops, stage, ops[k].b, at, depth);
1162 return;
1163 }
1164 Src::Val(k) => (Held::Value(k), stage[k]),
1165 Src::In(k) => (Held::Input(k), 0),
1166 };
1167 let d = depth.entry(held).or_insert(0);
1168 *d = (*d).max(at - born);
1169 }
1170 for i in 0..n {
1171 if needed[i] && !is_wiring(&ops[i]) {
1172 want(ops, &sched.stage, ops[i].a, sched.stage[i], &mut depth);
1173 want(ops, &sched.stage, ops[i].b, sched.stage[i], &mut depth);
1174 }
1175 }
1176 want(
1177 ops,
1178 &sched.stage,
1179 Src::Val(sched.result),
1180 sched.stage[sched.result],
1181 &mut depth,
1182 );
1183 Registers { depth, needed }
1184}
1185
1186impl PipelineSchedule {
1187 pub fn register_bits(&self, ops: &[FabricOp]) -> u64 {
1190 let known = known_bits(ops);
1191 let held: u64 = registers(ops, self)
1192 .depth
1193 .iter()
1194 .map(|(&h, &d)| match h {
1195 Held::Input(_) => 64 * (d as u64 + u64::from(self.inputs == Inputs::Registered)),
1196 Held::Value(k) => live_bits(known[k]) * d as u64,
1197 })
1198 .sum();
1199 held + live_bits(known[self.result])
1200 }
1201
1202 pub fn register_words(&self, ops: &[FabricOp]) -> usize {
1205 let regs = registers(ops, self);
1206 let inputs = match self.inputs {
1207 Inputs::Registered => regs.depth.keys().filter(|h| matches!(h, Held::Input(_))).count(),
1208 Inputs::Ports => 0,
1209 };
1210 inputs + regs.depth.values().sum::<usize>() + 1
1211 }
1212}
1213
1214fn held_name(h: Held, inputs: Inputs) -> String {
1215 match (h, inputs) {
1216 (Held::Input(k), Inputs::Registered) => format!("in{k}_r"),
1217 (Held::Input(k), Inputs::Ports) => format!("in{k}"),
1218 (Held::Value(k), _) => format!("v{k}"),
1219 }
1220}
1221
1222fn delayed_name(h: Held, d: usize, inputs: Inputs) -> String {
1223 if d == 0 {
1224 held_name(h, inputs)
1225 } else {
1226 format!("{}_d{d}", held_name(h, inputs))
1227 }
1228}
1229
1230fn const_verilog(c: i64) -> String {
1231 format!("64'h{:016x}", c as u64)
1232}
1233
1234struct Emission<'a> {
1235 ops: &'a [FabricOp],
1236 sched: &'a PipelineSchedule,
1237 rebuilt: std::collections::BTreeSet<(usize, usize)>,
1238 wires: String,
1239}
1240
1241impl Emission<'_> {
1242 fn operand(&mut self, s: Src, at: usize) -> String {
1244 match s {
1245 Src::Const(c) => const_verilog(c),
1246 Src::In(k) => delayed_name(Held::Input(k), at, self.sched.inputs),
1247 Src::Val(k) if is_wiring(&self.ops[k]) => {
1248 if self.rebuilt.insert((k, at)) {
1249 let op = self.ops[k];
1250 let a = self.operand(op.a, at);
1251 let b = self.operand(op.b, at);
1252 self.wires.push_str(&format!(
1253 " wire [63:0] w{k}_s{at} = {};\n",
1254 op.op.verilog(&a, &b)
1255 ));
1256 }
1257 format!("w{k}_s{at}")
1258 }
1259 Src::Val(k) => delayed_name(Held::Value(k), at - self.sched.stage[k], self.sched.inputs),
1260 }
1261 }
1262}
1263
1264pub fn emit_fabric_pipelined_verilog(
1267 name: &str,
1268 n_in: usize,
1269 ops: &[FabricOp],
1270 sched: &PipelineSchedule,
1271) -> String {
1272 sched
1273 .check(ops)
1274 .unwrap_or_else(|e| panic!("emitting an invalid schedule: {e}"));
1275 let regs = registers(ops, sched);
1276 let mut decls = String::new();
1277 let mut updates = String::new();
1278 let mode = sched.inputs;
1279 for (&held, &depth) in ®s.depth {
1280 if let (Held::Input(k), Inputs::Registered) = (held, mode) {
1281 decls.push_str(&format!(" reg [63:0] in{k}_r;\n"));
1282 updates.push_str(&format!(" in{k}_r <= in{k};\n"));
1283 }
1284 for d in 1..=depth {
1285 decls.push_str(&format!(" reg [63:0] {};\n", delayed_name(held, d, mode)));
1286 updates.push_str(&format!(
1287 " {} <= {};\n",
1288 delayed_name(held, d, mode),
1289 delayed_name(held, d - 1, mode)
1290 ));
1291 }
1292 }
1293 let mut em = Emission {
1294 ops,
1295 sched,
1296 rebuilt: Default::default(),
1297 wires: String::new(),
1298 };
1299 for (i, op) in ops.iter().enumerate() {
1300 if !regs.needed[i] || is_wiring(op) {
1301 continue;
1302 }
1303 let at = sched.stage[i];
1304 let a = em.operand(op.a, at);
1305 let b = em.operand(op.b, at);
1306 em.wires.push_str(&format!(
1307 " wire [63:0] v{i} = {};\n",
1308 op.op.verilog(&a, &b)
1309 ));
1310 }
1311 let out = em.operand(Src::Val(sched.result), sched.stage[sched.result]);
1312 updates.push_str(&format!(" result <= {out};\n"));
1313 let inputs = (0..n_in)
1314 .map(|k| format!("input [63:0] in{k}"))
1315 .collect::<Vec<_>>()
1316 .join(", ");
1317 let reads = match sched.inputs {
1318 Inputs::Registered => "",
1319 Inputs::Ports => "\n// Its first stage reads the ports: the registers that drive them hold its inputs, so none are registered twice.",
1320 };
1321 format!(
1322 "// fabric_{name} — a compiled dispatch-free residual as a {stages}-stage pipeline: a new input every clock,\n\
1323 // its result {latency} clocks later. Lowered from the REAL compiler's residual; multiplies and sums are\n\
1324 // carry-save rows reduced by 3:2 compressor trees and resolved by Kogge-Stone adds, cut into stages of at\n\
1325 // most {budget} XOR-depth units. 64-bit, so it wraps identically to the VM's Int; proven == the running VM by\n\
1326 // iverilog, streaming.{reads}\n\
1327 module fabric_{name}(input clk, {inputs}, output reg [63:0] result);\n\
1328 {decls}{wires} always @(posedge clk) begin\n\
1329 {updates} end\n\
1330 endmodule\n",
1331 stages = sched.stages(),
1332 latency = sched.latency(),
1333 budget = sched.budget,
1334 wires = em.wires,
1335 )
1336}
1337
1338pub fn simulate_pipeline(
1341 ops: &[FabricOp],
1342 sched: &PipelineSchedule,
1343 n_in: usize,
1344 stream: &[Vec<i64>],
1345) -> Vec<i64> {
1346 sched
1347 .check(ops)
1348 .unwrap_or_else(|e| panic!("simulating an invalid schedule: {e}"));
1349 let regs = registers(ops, sched);
1350 let mut held: std::collections::BTreeMap<(Held, usize), i64> = regs
1351 .depth
1352 .iter()
1353 .flat_map(|(&h, &depth)| {
1354 let first = if matches!(h, Held::Input(_)) && sched.inputs == Inputs::Registered { 0 } else { 1 };
1355 (first..=depth).map(move |d| ((h, d), 0i64))
1356 })
1357 .collect();
1358 let mut result_reg = 0i64;
1359 let mut out = Vec::with_capacity(stream.len());
1360 for inputs in stream {
1361 assert_eq!(inputs.len(), n_in, "every clock presents all {n_in} inputs");
1362 let mut comb = vec![0i64; ops.len()];
1363 let ports = (sched.inputs == Inputs::Ports).then_some(inputs.as_slice());
1364 #[allow(clippy::too_many_arguments)]
1365 fn read(
1366 ops: &[FabricOp],
1367 stage: &[usize],
1368 comb: &[i64],
1369 held: &std::collections::BTreeMap<(Held, usize), i64>,
1370 ports: Option<&[i64]>,
1371 s: Src,
1372 at: usize,
1373 ) -> i64 {
1374 match s {
1375 Src::Const(c) => c,
1376 Src::In(k) => match (ports, at) {
1377 (Some(now), 0) => now[k],
1378 _ => held[&(Held::Input(k), at)],
1379 },
1380 Src::Val(k) if is_wiring(&ops[k]) => {
1381 let op = ops[k];
1382 op.op.eval(
1383 read(ops, stage, comb, held, ports, op.a, at),
1384 read(ops, stage, comb, held, ports, op.b, at),
1385 )
1386 }
1387 Src::Val(k) if at == stage[k] => comb[k],
1388 Src::Val(k) => held[&(Held::Value(k), at - stage[k])],
1389 }
1390 }
1391 for (i, op) in ops.iter().enumerate() {
1392 if regs.needed[i] && !is_wiring(op) {
1393 let at = sched.stage[i];
1394 comb[i] = op.op.eval(
1395 read(ops, &sched.stage, &comb, &held, ports, op.a, at),
1396 read(ops, &sched.stage, &comb, &held, ports, op.b, at),
1397 );
1398 }
1399 }
1400 let next_result = read(
1401 ops,
1402 &sched.stage,
1403 &comb,
1404 &held,
1405 ports,
1406 Src::Val(sched.result),
1407 sched.stage[sched.result],
1408 );
1409 let mut next = held.clone();
1410 for (&(h, d), v) in next.iter_mut() {
1411 *v = match (h, d) {
1412 (Held::Input(k), 0) => inputs[k],
1413 (Held::Input(k), 1) if sched.inputs == Inputs::Ports => inputs[k],
1414 (Held::Value(k), 1) => comb[k],
1415 (h, d) => held[&(h, d - 1)],
1416 };
1417 }
1418 held = next;
1419 result_reg = next_result;
1420 out.push(result_reg);
1421 }
1422 out
1423}
1424
1425pub const HORNER_SEQ_BUDGET: f64 = 5.0;
1431
1432pub fn horner_seq_pipeline() -> (Vec<FabricOp>, PipelineSchedule) {
1435 let (ops, result, _) = residual_from_source(&poly_program("Show poly(4)."), "poly");
1436 let (lowered, result) = carry_save_lower(&ops, result, 64);
1437 let sched = schedule_pipeline(&lowered, result, HORNER_SEQ_BUDGET);
1438 (lowered, sched)
1439}
1440
1441pub fn emit_horner_seq_verilog() -> String {
1443 let (ops, sched) = horner_seq_pipeline();
1444 emit_fabric_pipelined_verilog("horner_seq", 1, &ops, &sched)
1445}
1446
1447pub fn dot_unrolled_source(u: usize) -> String {
1450 let terms: Vec<String> = (0..u)
1451 .map(|j| {
1452 if j == 0 {
1453 "k * (n - k)".into()
1454 } else {
1455 format!("(k + {j}) * (n - k - {j})")
1456 }
1457 })
1458 .collect();
1459 format!(
1460 "## To dotU (n: Int) -> Int:\n Let mutable s be 0.\n Repeat for k from 1 to n:\n Set s to s + {}.\n Return s.\n",
1461 terms.join(" + ")
1462 )
1463}
1464
1465pub fn matmul_array_top(name: &str) -> String {
1468 format!(
1469 "module matmul_array_top(input clk, input rst, input serin, output reg serout);\n\
1470 \x20 reg [63:0] in0 = 64'd0;\n wire [63:0] result; wire done;\n reg [63:0] acc = 64'd0;\n\
1471 \x20 cgra_{name} dut(.clk(clk), .rst(rst), .in0(in0), .result(result), .done(done));\n\
1472 \x20 always @(posedge clk) begin\n in0 <= {{in0[62:0], serin}};\n if (done) acc <= result;\n serout <= ^acc;\n end\nendmodule\n"
1473 )
1474}
1475
1476pub const GEMM_MUL_BITS: u32 = 8;
1478
1479pub const GEMM_TERM_BUDGET: f64 = 2.0;
1482
1483pub fn gemm_mac_array_verilog() -> String {
1486 let lf = loop_fabric_from_source(&dot_unrolled_source(8), "dotU");
1487 let pipelined = emit_loop_fabric_verilog_pipelined("arr8", &lf, Some(GEMM_MUL_BITS), GEMM_TERM_BUDGET)
1488 .unwrap_or_else(|why| panic!("the dot-product loop takes the pipelined form: {why}"));
1489 format!("{}\n{}", pipelined.verilog, matmul_array_top("arr8"))
1490}
1491
1492pub fn emit_csa_multiplier_verilog(module: &str) -> String {
1497 format!(
1498r#"// {module} — an unsigned AW×BW multiplier as a tree of 3:2 compressors over its BW partial-product rows. The
1499// compressor outputs register after CUT levels (CUT = 0 or beyond the tree's depth: no register inside it), the last
1500// two words register again when FINAL is set, and those two words resolve through a Kogge-Stone add. `p` is the
1501// product of the operands sampled as many clocks earlier as there are registers on its path.
1502module {module} #(parameter integer AW = 53, parameter integer BW = 53, parameter integer CUT = 4,
1503 parameter integer FINAL = 1) (
1504 input clk,
1505 input [AW-1:0] a,
1506 input [BW-1:0] b,
1507 output [AW+BW-1:0] p
1508);
1509 localparam integer W = AW + BW;
1510
1511 // The words entering tree level `level`: each level turns every three words into two and passes the rest.
1512 function integer words(input integer level);
1513 integer n, l;
1514 begin
1515 n = BW;
1516 for (l = 0; l < level; l = l + 1) n = 2 * (n / 3) + (n % 3);
1517 words = n;
1518 end
1519 endfunction
1520
1521 function integer depth(input integer unused);
1522 integer n, l;
1523 begin
1524 n = BW; l = 0;
1525 while (n > 2) begin n = 2 * (n / 3) + (n % 3); l = l + 1; end
1526 depth = l;
1527 end
1528 endfunction
1529
1530 localparam integer L = depth(0);
1531
1532 // Level-major bus: level `l` holds its words at word slots l*BW .. l*BW + words(l) - 1.
1533 wire [W*BW*(L+1)-1:0] lv;
1534
1535 genvar i, l, g;
1536 generate
1537 for (i = 0; i < BW; i = i + 1) begin : row
1538 assign lv[i*W +: W] = b[i] ? ({{{{BW{{1'b0}}}}, a}} << i) : {{W{{1'b0}}}};
1539 end
1540 for (l = 0; l < L; l = l + 1) begin : level
1541 localparam integer N = words(l);
1542 localparam integer G = N / 3;
1543 for (g = 0; g < G; g = g + 1) begin : compress
1544 wire [W-1:0] x = lv[((l*BW) + 3*g) * W +: W];
1545 wire [W-1:0] y = lv[((l*BW) + 3*g + 1) * W +: W];
1546 wire [W-1:0] z = lv[((l*BW) + 3*g + 2) * W +: W];
1547 wire [W-1:0] xy = x ^ y;
1548 wire [W-1:0] s = xy ^ z;
1549 wire [W-1:0] c = ((x & y) | (z & xy)) << 1;
1550 if (l + 1 == CUT) begin : registered
1551 reg [W-1:0] sr, cr;
1552 always @(posedge clk) begin sr <= s; cr <= c; end
1553 assign lv[(((l+1)*BW) + 2*g) * W +: W] = sr;
1554 assign lv[(((l+1)*BW) + 2*g + 1) * W +: W] = cr;
1555 end else begin : wired
1556 assign lv[(((l+1)*BW) + 2*g) * W +: W] = s;
1557 assign lv[(((l+1)*BW) + 2*g + 1) * W +: W] = c;
1558 end
1559 end
1560 for (g = 0; g < N % 3; g = g + 1) begin : carry_over
1561 if (l + 1 == CUT) begin : registered
1562 reg [W-1:0] r;
1563 always @(posedge clk) r <= lv[((l*BW) + 3*G + g) * W +: W];
1564 assign lv[(((l+1)*BW) + 2*G + g) * W +: W] = r;
1565 end else begin : wired
1566 assign lv[(((l+1)*BW) + 2*G + g) * W +: W] = lv[((l*BW) + 3*G + g) * W +: W];
1567 end
1568 end
1569 end
1570 endgenerate
1571
1572 wire [W-1:0] s_word = lv[(L*BW) * W +: W];
1573 wire [W-1:0] c_word;
1574 generate
1575 if (BW == 1) begin : one_row
1576 assign c_word = {{W{{1'b0}}}};
1577 end else begin : two_rows
1578 assign c_word = lv[(L*BW + 1) * W +: W];
1579 end
1580 endgenerate
1581 reg [W-1:0] s_r, c_r;
1582 always @(posedge clk) begin s_r <= s_word; c_r <= c_word; end
1583 wire [W-1:0] s_in = FINAL ? s_r : s_word;
1584 wire [W-1:0] c_in = FINAL ? c_r : c_word;
1585
1586 // The Kogge-Stone add of the last two words: group generate and propagate doubled in span each level.
1587 function [W-1:0] prefix_add(input [W-1:0] u, input [W-1:0] v);
1588 reg [W-1:0] gg, pp, p0; integer d;
1589 begin
1590 gg = u & v; p0 = u ^ v; pp = p0;
1591 for (d = 1; d < W; d = d * 2) begin
1592 gg = gg | (pp & (gg << d));
1593 pp = pp & (pp << d);
1594 end
1595 prefix_add = p0 ^ (gg << 1);
1596 end
1597 endfunction
1598
1599 assign p = prefix_add(s_in, c_in);
1600endmodule
1601"#
1602 )
1603}