1#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct Systolic {
22 pub k: usize,
24 pub n: usize,
26 pub weights: Vec<i64>,
28}
29
30impl Systolic {
31 pub fn new(k: usize, n: usize, weights: Vec<i64>) -> Systolic {
33 assert_eq!(weights.len(), k * n, "weights must be k*n row-major");
34 Systolic { k, n, weights }
35 }
36
37 pub fn w(&self, r: usize, c: usize) -> i64 {
39 self.weights[r * self.n + c]
40 }
41
42 pub fn weight_reads(&self) -> usize {
45 self.k * self.n
46 }
47
48 pub fn cycles(&self, m: usize) -> usize {
51 m
52 }
53}
54
55pub fn trunc_signed(x: i64, bits: u32) -> i64 {
58 if bits >= 64 {
59 x
60 } else {
61 let sh = 64 - bits;
62 (x << sh) >> sh
63 }
64}
65
66pub fn eval_systolic(sys: &Systolic, acts: &[i64], m: usize) -> Vec<i64> {
69 eval_systolic_bits(sys, acts, m, 8)
70}
71
72pub fn eval_systolic_bits(sys: &Systolic, acts: &[i64], m: usize, bits: u32) -> Vec<i64> {
75 assert_eq!(acts.len(), m * sys.k, "activations must be m*k row-major");
76 let mut out = vec![0i64; m * sys.n];
77 for i in 0..m {
78 for c in 0..sys.n {
79 let mut acc: i64 = 0;
80 for r in 0..sys.k {
81 let a = trunc_signed(acts[i * sys.k + r], bits);
82 let w = trunc_signed(sys.w(r, c), bits);
83 acc = acc.wrapping_add(a.wrapping_mul(w));
84 }
85 out[i * sys.n + c] = acc;
86 }
87 }
88 out
89}
90
91pub fn round_bf16(x: f64) -> f64 {
94 let f = x as f32;
95 if !f.is_finite() {
96 return f as f64;
97 }
98 let bits = f.to_bits();
99 let rounding_bias = 0x7fff + ((bits >> 16) & 1); let rounded = bits.wrapping_add(rounding_bias) & 0xFFFF_0000;
101 f32::from_bits(rounded) as f64
102}
103
104pub fn eval_systolic_bf16(weights: &[f64], acts: &[f64], m: usize, k: usize, n: usize) -> Vec<f64> {
107 assert_eq!(weights.len(), k * n);
108 assert_eq!(acts.len(), m * k);
109 let mut out = vec![0f64; m * n];
110 for i in 0..m {
111 for c in 0..n {
112 let mut acc: f32 = 0.0;
113 for r in 0..k {
114 let a = round_bf16(acts[i * k + r]) as f32;
115 let w = round_bf16(weights[r * n + c]) as f32;
116 acc += a * w;
117 }
118 out[i * n + c] = acc as f64;
119 }
120 }
121 out
122}
123
124pub fn eval_systolic_f32(weights: &[f64], acts: &[f64], m: usize, k: usize, n: usize) -> Vec<f64> {
127 assert_eq!(weights.len(), k * n);
128 assert_eq!(acts.len(), m * k);
129 let mut out = vec![0f64; m * n];
130 for i in 0..m {
131 for c in 0..n {
132 let mut acc: f32 = 0.0;
133 for r in 0..k {
134 acc += (acts[i * k + r] as f32) * (weights[r * n + c] as f32);
135 }
136 out[i * n + c] = acc as f64;
137 }
138 }
139 out
140}
141
142pub fn eval_tiled_gemm(sys: &Systolic, acts: &[i64], m: usize, tk: usize, tn: usize) -> Vec<i64> {
147 let (k, n) = (sys.k, sys.n);
148 assert!(tk >= 1 && tn >= 1, "tile dims must be positive");
149 assert_eq!(acts.len(), m * k, "activations must be m*k row-major");
150 let mut out = vec![0i64; m * n];
151 let mut c0 = 0;
152 while c0 < n {
153 let cn = (c0 + tn).min(n);
154 let mut r0 = 0;
155 while r0 < k {
156 let rk = (r0 + tk).min(k);
157 let (sub_k, sub_n) = (rk - r0, cn - c0);
158 let mut wsub = Vec::with_capacity(sub_k * sub_n);
160 for r in r0..rk {
161 for c in c0..cn {
162 wsub.push(sys.w(r, c));
163 }
164 }
165 let sub = Systolic::new(sub_k, sub_n, wsub);
166 let mut asub = Vec::with_capacity(m * sub_k);
167 for i in 0..m {
168 for r in r0..rk {
169 asub.push(acts[i * k + r]);
170 }
171 }
172 let osub = eval_systolic(&sub, &asub, m); for i in 0..m {
174 for cc in 0..sub_n {
175 let o = &mut out[i * n + (c0 + cc)];
176 *o = o.wrapping_add(osub[i * sub_n + cc]);
177 }
178 }
179 r0 = rk;
180 }
181 c0 = cn;
182 }
183 out
184}
185
186#[derive(Clone, Debug)]
190pub struct FusedBlock {
191 pub sys: Systolic,
192 pub requant_m: i64,
193 pub requant_s: u32,
194 pub bias: Vec<i64>,
196}
197
198pub fn eval_fused_block(fb: &FusedBlock, acts: &[i64], m: usize) -> Vec<i64> {
202 let n = fb.sys.n;
203 assert_eq!(
204 fb.bias.len(),
205 n,
206 "bias must have one entry per output column"
207 );
208 let gemm = eval_systolic(&fb.sys, acts, m);
209 let mut out = vec![0i64; m * n];
210 for i in 0..m {
211 for c in 0..n {
212 let rq = fb.requant_m.wrapping_mul(gemm[i * n + c]) >> fb.requant_s; let biased = rq.wrapping_add(fb.bias[c]);
214 out[i * n + c] = biased.max(0); }
216 }
217 out
218}
219
220pub fn eval_unfused_block(fb: &FusedBlock, acts: &[i64], m: usize) -> (Vec<i64>, usize) {
224 let n = fb.sys.n;
225 let gemm = eval_systolic(&fb.sys, acts, m); let mut requant = vec![0i64; m * n]; for i in 0..m {
228 for c in 0..n {
229 requant[i * n + c] = fb.requant_m.wrapping_mul(gemm[i * n + c]) >> fb.requant_s;
230 }
231 }
232 let mut out = vec![0i64; m * n];
233 for i in 0..m {
234 for c in 0..n {
235 out[i * n + c] = requant[i * n + c].wrapping_add(fb.bias[c]).max(0);
236 }
237 }
238 (out, 2 * m * n) }
240
241#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
251pub enum SystolicSchedule {
252 #[default]
253 Combinational,
254 Pipelined,
255}
256
257pub fn emit_systolic_scheduled(
261 name: &str,
262 sys: &Systolic,
263 schedule: SystolicSchedule,
264 bits: u32,
265) -> String {
266 match schedule {
267 SystolicSchedule::Combinational => emit_systolic_verilog_bits(name, sys, bits),
268 SystolicSchedule::Pipelined => emit_systolic_pipelined_verilog_bits(name, sys, bits),
269 }
270}
271
272pub fn emit_systolic_verilog(name: &str, sys: &Systolic) -> String {
277 emit_systolic_verilog_bits(name, sys, 8)
278}
279
280pub fn emit_systolic_verilog_bits(name: &str, sys: &Systolic, bits: u32) -> String {
284 let (k, n) = (sys.k, sys.n);
285 let hi = bits.min(64) - 1;
286 let mut s = String::new();
287 s.push_str(&format!("module {name}(\n input clk,\n input rst,\n"));
288 for r in 0..k {
289 s.push_str(&format!(" input signed [63:0] a{r},\n"));
290 }
291 for c in 0..n {
292 let comma = if c + 1 < n { "," } else { "" };
293 s.push_str(&format!(" output reg signed [63:0] o{c}{comma}\n"));
294 }
295 s.push_str(");\n");
296 for r in 0..k {
298 s.push_str(&format!(
299 " wire signed [63:0] sa{r} = $signed(a{r}[{hi}:0]);\n"
300 ));
301 }
302 s.push_str(" always @(posedge clk) begin\n if (rst) begin\n");
303 for c in 0..n {
304 s.push_str(&format!(" o{c} <= 64'sd0;\n"));
305 }
306 s.push_str(" end else begin\n");
307 for c in 0..n {
308 let terms: Vec<String> = (0..k)
309 .map(|r| {
310 let w = trunc_signed(sys.w(r, c), bits);
311 format!("sa{r} * 64'sh{:016X}", w as u64)
312 })
313 .collect();
314 s.push_str(&format!(" o{c} <= {};\n", terms.join(" + ")));
315 }
316 s.push_str(" end\n end\nendmodule\n");
317 s
318}
319
320pub fn pipelined_latency(k: usize, n: usize) -> usize {
332 k + n - 1
333}
334
335pub fn eval_systolic_pipelined(
340 sys: &Systolic,
341 acts: &[i64],
342 m: usize,
343 bits: u32,
344) -> (Vec<i64>, usize) {
345 eval_systolic_pipelined_acc(sys, acts, m, bits, ACC_BITS_FULL)
346}
347
348pub fn eval_systolic_pipelined_acc(
353 sys: &Systolic,
354 acts: &[i64],
355 m: usize,
356 bits: u32,
357 acc_bits: u32,
358) -> (Vec<i64>, usize) {
359 assert_eq!(acts.len(), m * sys.k, "activations must be m*k row-major");
360 let (k, n) = (sys.k, sys.n);
361 let l = pipelined_latency(k, n);
362 let w = |r: usize, c: usize| trunc_signed(sys.w(r, c), bits);
363 let mut a_reg = vec![0i64; k * n]; let mut ps_reg = vec![0i64; k * n]; let mut skew: Vec<Vec<i64>> = (0..k).map(|r| vec![0i64; r]).collect(); let mut deskew: Vec<Vec<i64>> = (0..n).map(|c| vec![0i64; n - 1 - c]).collect(); let mut out = vec![0i64; m * n];
368 for t in 0..(m + l + 1) {
369 let a_top: Vec<i64> = (0..k)
371 .map(|r| {
372 if t < m {
373 trunc_signed(acts[t * k + r], bits)
374 } else {
375 0
376 }
377 })
378 .collect();
379 let mut a_skew = vec![0i64; k];
380 for r in 0..k {
381 if r == 0 {
382 a_skew[0] = a_top[0];
383 } else {
384 a_skew[r] = skew[r][r - 1]; for j in (1..r).rev() {
386 skew[r][j] = skew[r][j - 1];
387 }
388 skew[r][0] = a_top[r];
389 }
390 }
391 let mut a_next = vec![0i64; k * n];
393 let mut ps_next = vec![0i64; k * n];
394 for r in 0..k {
395 for c in 0..n {
396 let a_in = if c == 0 {
397 a_skew[r]
398 } else {
399 a_reg[r * n + (c - 1)]
400 };
401 let ps_in = if r == 0 { 0 } else { ps_reg[(r - 1) * n + c] };
402 a_next[r * n + c] = a_in;
403 ps_next[r * n + c] =
404 trunc_signed(ps_in.wrapping_add(a_in.wrapping_mul(w(r, c))), acc_bits);
405 }
406 }
407 let mut o_now = vec![0i64; n];
409 for c in 0..n {
410 let bottom = ps_reg[(k - 1) * n + c];
411 let d = n - 1 - c;
412 if d == 0 {
413 o_now[c] = bottom;
414 } else {
415 o_now[c] = deskew[c][d - 1];
416 for j in (1..d).rev() {
417 deskew[c][j] = deskew[c][j - 1];
418 }
419 deskew[c][0] = bottom;
420 }
421 }
422 if t >= l && (t - l) < m {
424 let i = t - l;
425 out[i * n..i * n + n].copy_from_slice(&o_now);
426 }
427 a_reg = a_next;
428 ps_reg = ps_next;
429 }
430 (out, l)
431}
432
433pub fn eval_systolic_pipelined_o(sys: &Systolic, acts: &[i64], m: usize, bits: u32) -> Vec<i64> {
435 eval_systolic_pipelined(sys, acts, m, bits).0
436}
437
438pub fn emit_systolic_pipelined_verilog(name: &str, sys: &Systolic) -> String {
441 emit_systolic_pipelined_verilog_bits(name, sys, 8)
442}
443
444pub const ACC_BITS_FULL: u32 = 64;
447pub const ACC_BITS_MXU: u32 = 32;
450
451pub fn emit_systolic_pipelined_verilog_bits(name: &str, sys: &Systolic, bits: u32) -> String {
453 emit_systolic_pipelined_verilog_bits_acc(name, sys, bits, ACC_BITS_FULL)
454}
455
456pub fn emit_systolic_pipelined_verilog_bits_acc(
466 name: &str,
467 sys: &Systolic,
468 bits: u32,
469 acc_bits: u32,
470) -> String {
471 pe_array_verilog(name, sys.k, sys.n, bits, acc_bits, WeightSource::Baked(sys))
472}
473
474#[derive(Clone, Copy)]
478pub enum WeightSource<'a> {
479 Baked(&'a Systolic),
480 Loaded(WeightStart),
481}
482
483#[derive(Clone, Copy, Debug, PartialEq, Eq)]
485pub enum WeightStart {
486 PowerUp,
489 Zero,
494}
495
496pub fn emit_systolic_pipelined_loadable_verilog(
500 name: &str,
501 k: usize,
502 n: usize,
503 bits: u32,
504 acc_bits: u32,
505) -> String {
506 emit_systolic_pipelined_loadable_verilog_starting(name, k, n, bits, acc_bits, WeightStart::PowerUp)
507}
508
509pub fn emit_systolic_pipelined_loadable_verilog_starting(
511 name: &str,
512 k: usize,
513 n: usize,
514 bits: u32,
515 acc_bits: u32,
516 start: WeightStart,
517) -> String {
518 pe_array_verilog(name, k, n, bits, acc_bits, WeightSource::Loaded(start))
519}
520
521fn pe_array_verilog(
522 name: &str,
523 k: usize,
524 n: usize,
525 bits: u32,
526 acc_bits: u32,
527 weights: WeightSource,
528) -> String {
529 let hi = bits.min(64) - 1;
530 let aw = bits.min(64); let pw = (2 * bits).min(acc_bits); let ah = acc_bits.min(64) - 1; let mut s = String::new();
536 s.push_str(&format!("module {name}(\n input clk,\n input rst,\n"));
537 if matches!(weights, WeightSource::Loaded(_)) {
538 let rw = (usize::BITS - (k.max(2) - 1).leading_zeros()).max(1);
539 s.push_str(&format!(
540 " input w_load,\n input [{}:0] w_row,\n input [{}:0] w_data,\n",
541 rw - 1,
542 n as u32 * bits - 1
543 ));
544 }
545 for r in 0..k {
546 s.push_str(&format!(" input signed [63:0] a{r},\n"));
547 }
548 for c in 0..n {
549 let comma = if c + 1 < n { "," } else { "" };
550 s.push_str(&format!(" output signed [63:0] o{c}{comma}\n"));
551 }
552 s.push_str(");\n");
553 for r in 0..k {
554 s.push_str(&format!(
555 " wire signed [{}:0] sa{r} = $signed(a{r}[{hi}:0]);\n",
556 aw - 1
557 ));
558 }
559 for r in 0..k {
561 for j in 0..r {
562 s.push_str(&format!(" reg signed [{}:0] skew_{r}_{j};\n", aw - 1));
563 }
564 }
565 for r in 0..k {
566 if r == 0 {
567 s.push_str(&format!(" wire signed [{}:0] a_sk0 = sa0;\n", aw - 1));
568 } else {
569 s.push_str(&format!(
570 " wire signed [{}:0] a_sk{r} = skew_{r}_{};\n",
571 aw - 1,
572 r - 1
573 ));
574 }
575 }
576 for r in 0..k {
578 for c in 0..n {
579 s.push_str(&format!(" reg signed [{}:0] a_reg_{r}_{c};\n", aw - 1));
580 s.push_str(&format!(" reg signed [{ah}:0] ps_reg_{r}_{c};\n"));
581 let a_in = if c == 0 {
582 format!("a_sk{r}")
583 } else {
584 format!("a_reg_{r}_{}", c - 1)
585 };
586 s.push_str(&format!(
587 " wire signed [{}:0] a_in_{r}_{c} = {a_in};\n",
588 aw - 1
589 ));
590 let ps_in = if r == 0 {
591 format!("{acc_bits}'sd0")
592 } else {
593 format!("ps_reg_{}_{c}", r - 1)
594 };
595 s.push_str(&format!(
596 " wire signed [{ah}:0] ps_in_{r}_{c} = {ps_in};\n"
597 ));
598 match weights {
600 WeightSource::Baked(sys) => {
601 let w = trunc_signed(sys.w(r, c), bits);
602 let wmask = if bits >= 64 {
603 u64::MAX
604 } else {
605 (1u64 << bits) - 1
606 };
607 let wv = (w as u64) & wmask;
608 s.push_str(&format!(
609 " wire signed [{}:0] prod_{r}_{c} = a_in_{r}_{c} * {bits}'sh{wv:X};\n",
610 pw - 1
611 ));
612 }
613 WeightSource::Loaded(start) => {
614 let init = match start {
615 WeightStart::PowerUp => String::new(),
616 WeightStart::Zero => format!(" = {bits}'sd0"),
617 };
618 s.push_str(&format!(" reg signed [{}:0] w_reg_{r}_{c}{init};\n", bits - 1));
619 s.push_str(&format!(
620 " wire signed [{}:0] prod_{r}_{c} = a_in_{r}_{c} * w_reg_{r}_{c};\n",
621 pw - 1
622 ));
623 }
624 }
625 }
626 }
627 for c in 0..n {
629 let d = n - 1 - c;
630 for j in 0..d {
631 s.push_str(&format!(" reg signed [{ah}:0] deskew_{c}_{j};\n"));
632 }
633 if d == 0 {
634 s.push_str(&format!(" assign o{c} = ps_reg_{}_{c};\n", k - 1));
635 } else {
636 s.push_str(&format!(" assign o{c} = deskew_{c}_{};\n", d - 1));
637 }
638 }
639 s.push_str(" always @(posedge clk) begin\n if (rst) begin\n");
640 for r in 0..k {
641 for j in 0..r {
642 s.push_str(&format!(" skew_{r}_{j} <= {aw}'sd0;\n"));
643 }
644 }
645 for r in 0..k {
646 for c in 0..n {
647 s.push_str(&format!(" a_reg_{r}_{c} <= {aw}'sd0;\n"));
648 s.push_str(&format!(" ps_reg_{r}_{c} <= {acc_bits}'sd0;\n"));
649 }
650 }
651 for c in 0..n {
652 for j in 0..(n - 1 - c) {
653 s.push_str(&format!(" deskew_{c}_{j} <= {acc_bits}'sd0;\n"));
654 }
655 }
656 s.push_str(" end else begin\n");
657 for r in 1..k {
658 s.push_str(&format!(" skew_{r}_0 <= sa{r};\n"));
659 for j in 1..r {
660 s.push_str(&format!(" skew_{r}_{j} <= skew_{r}_{};\n", j - 1));
661 }
662 }
663 for r in 0..k {
664 for c in 0..n {
665 s.push_str(&format!(" a_reg_{r}_{c} <= a_in_{r}_{c};\n"));
666 s.push_str(&format!(
667 " ps_reg_{r}_{c} <= ps_in_{r}_{c} + prod_{r}_{c};\n"
668 ));
669 }
670 }
671 for c in 0..n {
672 let d = n - 1 - c;
673 if d > 0 {
674 s.push_str(&format!(" deskew_{c}_0 <= ps_reg_{}_{c};\n", k - 1));
675 for j in 1..d {
676 s.push_str(&format!(" deskew_{c}_{j} <= deskew_{c}_{};\n", j - 1));
677 }
678 }
679 }
680 s.push_str(" end\n end\n");
681 if matches!(weights, WeightSource::Loaded(_)) {
682 s.push_str(" always @(posedge clk) begin\n if (w_load) begin\n");
683 for r in 0..k {
684 s.push_str(&format!(" if (w_row == {r}) begin\n"));
685 for c in 0..n {
686 s.push_str(&format!(
687 " w_reg_{r}_{c} <= $signed(w_data[{}:{}]);\n",
688 (c as u32 + 1) * bits - 1,
689 c as u32 * bits
690 ));
691 }
692 s.push_str(" end\n");
693 }
694 s.push_str(" end\n end\n");
695 }
696 s.push_str("endmodule\n");
697 s
698}
699
700pub fn emit_loadable_vs_baked_testbench(
705 baked: &str,
706 loadable: &str,
707 sys: &Systolic,
708 acts: &[i64],
709 m: usize,
710 bits: u32,
711 acc_bits: u32,
712) -> String {
713 let (k, n) = (sys.k, sys.n);
714 let (_, l) = eval_systolic_pipelined_acc(sys, acts, m, bits, acc_bits);
715 let rw = (usize::BITS - (k.max(2) - 1).leading_zeros()).max(1);
716 let wmask: u64 = if bits >= 64 {
717 u64::MAX
718 } else {
719 (1u64 << bits) - 1
720 };
721 let mut tb =
722 String::from("`timescale 1ns/1ps\nmodule tb_pe;\n reg clk = 0, rst = 1, w_load = 0;\n");
723 tb.push_str(&format!(
724 " reg [{}:0] w_row = 0;\n reg [{}:0] w_data = 0;\n",
725 rw - 1,
726 n as u32 * bits - 1
727 ));
728 for r in 0..k {
729 tb.push_str(&format!(" reg signed [63:0] a{r} = 0;\n"));
730 }
731 for c in 0..n {
732 tb.push_str(&format!(" wire signed [63:0] ob{c}, ol{c};\n"));
733 }
734 let ac: Vec<String> = (0..k).map(|r| format!(".a{r}(a{r})")).collect();
735 let ob: Vec<String> = (0..n).map(|c| format!(".o{c}(ob{c})")).collect();
736 let ol: Vec<String> = (0..n).map(|c| format!(".o{c}(ol{c})")).collect();
737 tb.push_str(&format!(
738 " {baked} u_baked(.clk(clk), .rst(rst), {}, {});\n",
739 ac.join(", "),
740 ob.join(", ")
741 ));
742 tb.push_str(&format!(" {loadable} u_loadable(.clk(clk), .rst(rst), .w_load(w_load), .w_row(w_row), .w_data(w_data), {}, {});\n", ac.join(", "), ol.join(", ")));
743 tb.push_str(" integer fails = 0;\n always #5 clk = ~clk;\n initial begin\n rst = 1; @(posedge clk); #1;\n");
744 for r in 0..k {
745 let mut packed = String::new();
747 for c in (0..n).rev() {
748 let w = (trunc_signed(sys.w(r, c), bits) as u64) & wmask;
749 packed.push_str(&format!("{:0width$b}", w, width = bits as usize));
750 }
751 tb.push_str(&format!(
752 " w_load = 1; w_row = {r}; w_data = {}'b{packed}; @(posedge clk); #1;\n",
753 n as u32 * bits
754 ));
755 }
756 tb.push_str(" w_load = 0; @(posedge clk); #1 rst = 0;\n");
757 let cmp: Vec<String> = (0..n).map(|c| format!("ob{c} !== ol{c}")).collect();
758 for e in 0..(m + l) {
759 for r in 0..k {
760 let v = if e < m { acts[e * k + r] } else { 0 };
761 tb.push_str(&format!(" a{r} = 64'sh{:016X};\n", v as u64));
762 }
763 tb.push_str(" @(posedge clk); #1;\n");
764 tb.push_str(&format!(
765 " if ({}) begin $display(\"MISMATCH at edge {e}\"); fails = fails + 1; end\n",
766 cmp.join(" || ")
767 ));
768 if e + 1 >= l && (e + 1 - l) < m {
769 let i = e + 1 - l;
770 for c in 0..n {
771 tb.push_str(&format!(
772 " $display(\"OUT o {} %0d\", ol{c});\n",
773 i * n + c
774 ));
775 }
776 }
777 }
778 tb.push_str(&format!(" $display(\"LATENCY {l}\");\n"));
779 tb.push_str(" if (fails == 0) $display(\"PASS: loadable == baked\"); else $display(\"FAIL: %0d cycles differ\", fails);\n $finish;\n end\nendmodule\n");
780 tb
781}
782
783#[derive(Clone, Debug)]
792pub struct SystolicOs {
793 pub r: usize,
794 pub c: usize,
795 pub k: usize,
796}
797
798impl SystolicOs {
799 pub fn new(r: usize, c: usize, k: usize) -> Self {
800 SystolicOs { r, c, k }
801 }
802}
803
804pub fn eval_systolic_os(os: &SystolicOs, a: &[i64], w: &[i64], bits: u32) -> Vec<i64> {
806 let (r, c, k) = (os.r, os.c, os.k);
807 let mut o = vec![0i64; r * c];
808 for i in 0..r {
809 for j in 0..c {
810 let mut acc = 0i64;
811 for t in 0..k {
812 let ai = trunc_signed(a[i * k + t], bits);
813 let wj = trunc_signed(w[t * c + j], bits);
814 acc = acc.wrapping_add(ai.wrapping_mul(wj));
815 }
816 o[i * c + j] = acc;
817 }
818 }
819 o
820}
821
822pub fn pipelined_os_latency(r: usize, c: usize, k: usize) -> usize {
824 k + r + c - 2
825}
826
827pub fn eval_systolic_os_pipelined(
833 os: &SystolicOs,
834 a: &[i64],
835 w: &[i64],
836 bits: u32,
837) -> (Vec<i64>, usize) {
838 eval_systolic_os_pipelined_acc(os, a, w, bits, ACC_BITS_FULL)
839}
840
841pub fn eval_systolic_os_pipelined_acc(
844 os: &SystolicOs,
845 a: &[i64],
846 w: &[i64],
847 bits: u32,
848 acc_bits: u32,
849) -> (Vec<i64>, usize) {
850 let (r, c, k) = (os.r, os.c, os.k);
851 let mut acc = vec![0i64; r * c];
852 let mut a_reg = vec![0i64; r * c];
853 let mut w_reg = vec![0i64; r * c];
854 let l = pipelined_os_latency(r, c, k);
855 for t_cyc in 0..=l {
857 let a_west = |i: usize| -> i64 {
859 if t_cyc >= i && t_cyc - i < k {
861 trunc_signed(a[i * k + (t_cyc - i)], bits)
862 } else {
863 0
864 }
865 };
866 let w_north = |j: usize| -> i64 {
867 if t_cyc >= j && t_cyc - j < k {
868 trunc_signed(w[(t_cyc - j) * c + j], bits)
869 } else {
870 0
871 }
872 };
873 let mut a_next = vec![0i64; r * c];
874 let mut w_next = vec![0i64; r * c];
875 let mut acc_next = acc.clone();
876 for i in 0..r {
877 for j in 0..c {
878 let a_in = if j == 0 {
879 a_west(i)
880 } else {
881 a_reg[i * c + (j - 1)]
882 };
883 let w_in = if i == 0 {
884 w_north(j)
885 } else {
886 w_reg[(i - 1) * c + j]
887 };
888 a_next[i * c + j] = a_in;
889 w_next[i * c + j] = w_in;
890 acc_next[i * c + j] = trunc_signed(
891 acc[i * c + j].wrapping_add(a_in.wrapping_mul(w_in)),
892 acc_bits,
893 );
894 }
895 }
896 a_reg = a_next;
897 w_reg = w_next;
898 acc = acc_next;
899 }
900 (acc, l)
901}
902
903pub fn eval_systolic_os_pipelined_o(os: &SystolicOs, a: &[i64], w: &[i64], bits: u32) -> Vec<i64> {
905 eval_systolic_os_pipelined(os, a, w, bits).0
906}
907
908pub fn emit_systolic_os_pipelined_verilog(name: &str, os: &SystolicOs) -> String {
911 emit_systolic_os_pipelined_verilog_bits(name, os, 8)
912}
913
914pub fn emit_systolic_os_pipelined_verilog_bits(name: &str, os: &SystolicOs, bits: u32) -> String {
916 emit_systolic_os_pipelined_verilog_bits_acc(name, os, bits, ACC_BITS_FULL)
917}
918
919pub fn emit_systolic_os_pipelined_verilog_bits_acc(
926 name: &str,
927 os: &SystolicOs,
928 bits: u32,
929 acc_bits: u32,
930) -> String {
931 let (r, c) = (os.r, os.c);
932 let hi = bits.min(64) - 1;
933 let ow = bits.min(64); let pw = (2 * bits).min(acc_bits); let ah = acc_bits.min(64) - 1; let mut s = String::new();
939 s.push_str(&format!("module {name}(\n input clk,\n input rst,\n"));
940 for i in 0..r {
941 s.push_str(&format!(" input signed [63:0] aw{i},\n"));
942 }
943 for j in 0..c {
944 s.push_str(&format!(" input signed [63:0] wn{j},\n"));
945 }
946 for i in 0..r {
947 for j in 0..c {
948 let last = i + 1 == r && j + 1 == c;
949 let comma = if last { "" } else { "," };
950 s.push_str(&format!(" output signed [63:0] acc{i}_{j}{comma}\n"));
951 }
952 }
953 s.push_str(");\n");
954 for i in 0..r {
956 s.push_str(&format!(
957 " wire signed [{}:0] saw{i} = $signed(aw{i}[{hi}:0]);\n",
958 ow - 1
959 ));
960 }
961 for j in 0..c {
962 s.push_str(&format!(
963 " wire signed [{}:0] swn{j} = $signed(wn{j}[{hi}:0]);\n",
964 ow - 1
965 ));
966 }
967 for i in 0..r {
969 for d in 0..i {
970 s.push_str(&format!(" reg signed [{}:0] askew_{i}_{d};\n", ow - 1));
971 }
972 }
973 for j in 0..c {
974 for d in 0..j {
975 s.push_str(&format!(" reg signed [{}:0] wskew_{j}_{d};\n", ow - 1));
976 }
977 }
978 for i in 0..r {
979 if i == 0 {
980 s.push_str(&format!(" wire signed [{}:0] a_edge0 = saw0;\n", ow - 1));
981 } else {
982 s.push_str(&format!(
983 " wire signed [{}:0] a_edge{i} = askew_{i}_{};\n",
984 ow - 1,
985 i - 1
986 ));
987 }
988 }
989 for j in 0..c {
990 if j == 0 {
991 s.push_str(&format!(" wire signed [{}:0] w_edge0 = swn0;\n", ow - 1));
992 } else {
993 s.push_str(&format!(
994 " wire signed [{}:0] w_edge{j} = wskew_{j}_{};\n",
995 ow - 1,
996 j - 1
997 ));
998 }
999 }
1000 for i in 0..r {
1002 for j in 0..c {
1003 s.push_str(&format!(" reg signed [{}:0] a_reg_{i}_{j};\n", ow - 1));
1004 s.push_str(&format!(" reg signed [{}:0] w_reg_{i}_{j};\n", ow - 1));
1005 s.push_str(&format!(" reg signed [{ah}:0] acc_reg_{i}_{j};\n"));
1006 let a_in = if j == 0 {
1007 format!("a_edge{i}")
1008 } else {
1009 format!("a_reg_{i}_{}", j - 1)
1010 };
1011 let w_in = if i == 0 {
1012 format!("w_edge{j}")
1013 } else {
1014 format!("w_reg_{}_{j}", i - 1)
1015 };
1016 s.push_str(&format!(
1017 " wire signed [{}:0] a_in_{i}_{j} = {a_in};\n",
1018 ow - 1
1019 ));
1020 s.push_str(&format!(
1021 " wire signed [{}:0] w_in_{i}_{j} = {w_in};\n",
1022 ow - 1
1023 ));
1024 s.push_str(&format!(
1025 " wire signed [{}:0] prod_{i}_{j} = a_in_{i}_{j} * w_in_{i}_{j};\n",
1026 pw - 1
1027 ));
1028 s.push_str(&format!(" assign acc{i}_{j} = acc_reg_{i}_{j};\n"));
1029 }
1030 }
1031 s.push_str(" always @(posedge clk) begin\n if (rst) begin\n");
1032 for i in 0..r {
1033 for d in 0..i {
1034 s.push_str(&format!(" askew_{i}_{d} <= {ow}'sd0;\n"));
1035 }
1036 }
1037 for j in 0..c {
1038 for d in 0..j {
1039 s.push_str(&format!(" wskew_{j}_{d} <= {ow}'sd0;\n"));
1040 }
1041 }
1042 for i in 0..r {
1043 for j in 0..c {
1044 s.push_str(&format!(" a_reg_{i}_{j} <= {ow}'sd0;\n"));
1045 s.push_str(&format!(" w_reg_{i}_{j} <= {ow}'sd0;\n"));
1046 s.push_str(&format!(" acc_reg_{i}_{j} <= {acc_bits}'sd0;\n"));
1047 }
1048 }
1049 s.push_str(" end else begin\n");
1050 for i in 1..r {
1051 s.push_str(&format!(" askew_{i}_0 <= saw{i};\n"));
1052 for d in 1..i {
1053 s.push_str(&format!(" askew_{i}_{d} <= askew_{i}_{};\n", d - 1));
1054 }
1055 }
1056 for j in 1..c {
1057 s.push_str(&format!(" wskew_{j}_0 <= swn{j};\n"));
1058 for d in 1..j {
1059 s.push_str(&format!(" wskew_{j}_{d} <= wskew_{j}_{};\n", d - 1));
1060 }
1061 }
1062 for i in 0..r {
1063 for j in 0..c {
1064 s.push_str(&format!(" a_reg_{i}_{j} <= a_in_{i}_{j};\n"));
1065 s.push_str(&format!(" w_reg_{i}_{j} <= w_in_{i}_{j};\n"));
1066 s.push_str(&format!(
1067 " acc_reg_{i}_{j} <= acc_reg_{i}_{j} + prod_{i}_{j};\n"
1068 ));
1069 }
1070 }
1071 s.push_str(" end\n end\nendmodule\n");
1072 s
1073}
1074
1075pub fn bist_tile_weights(k: usize, n: usize) -> Vec<i64> {
1077 (0..k * n)
1078 .map(|i| {
1079 let mut x = (i as u64).wrapping_mul(0x9e3779b97f4a7c15).wrapping_add(1);
1080 x ^= x >> 29;
1081 ((x % 255) as i64) - 127
1082 })
1083 .collect()
1084}
1085
1086pub fn emit_systolic_bist_wrapper(core: &str, k: usize, n: usize) -> String {
1090 let a_wires: String = (0..k)
1091 .map(|r| format!(" wire signed [63:0] a{r} = $signed(lfsr[{}:{}]);\n", r * 8 % 57 + 7, r * 8 % 57))
1092 .collect();
1093 let o_wires: String = (0..n).map(|c| format!(" wire signed [63:0] o{c};\n")).collect();
1094 let aconn: Vec<String> = (0..k).map(|r| format!(".a{r}(a{r})")).collect();
1095 let oconn: Vec<String> = (0..n).map(|c| format!(".o{c}(o{c})")).collect();
1096 let ofold: String = (0..n).map(|c| format!("o{c}")).collect::<Vec<_>>().join(" ^ ");
1097 format!(
1098 "module {core}_realbist(input clk, input rst, output sig);\n\
1099 \x20 reg [63:0] lfsr;\n reg [63:0] misr;\n{a_wires}{o_wires}\
1100 \x20 {core} arr(.clk(clk), .rst(rst), {aconn}, {oconn});\n\
1101 \x20 always @(posedge clk) begin\n\
1102 \x20 if (rst) begin\n lfsr <= 64'hD1CEF00D12345678;\n misr <= 64'd0;\n end else begin\n\
1103 \x20 lfsr <= {{lfsr[62:0], lfsr[63]^lfsr[62]^lfsr[60]^lfsr[59]}};\n\
1104 \x20 misr <= {{misr[62:0], misr[63]}} ^ ({ofold});\n end\n end\n\
1105 \x20 assign sig = ^misr;\nendmodule\n",
1106 aconn = aconn.join(", "),
1107 oconn = oconn.join(", "),
1108 )
1109}
1110
1111pub fn emit_systolic_os_bist_wrapper(core: &str, r: usize, c: usize) -> String {
1117 let aw: String = (0..r)
1118 .map(|i| format!(" wire signed [63:0] aw{i} = $signed(lfsr[{}:{}]);\n", i * 8 % 57 + 7, i * 8 % 57))
1119 .collect();
1120 let wn: String = (0..c)
1121 .map(|j| format!(" wire signed [63:0] wn{j} = $signed(lfsr[{}:{}]);\n", (j * 8 + 4) % 57 + 7, (j * 8 + 4) % 57))
1122 .collect();
1123 let accw: String = (0..r)
1124 .flat_map(|i| (0..c).map(move |j| format!(" wire signed [63:0] acc{i}_{j};\n")))
1125 .collect();
1126 let conns: Vec<String> = (0..r)
1127 .map(|i| format!(".aw{i}(aw{i})"))
1128 .chain((0..c).map(|j| format!(".wn{j}(wn{j})")))
1129 .chain((0..r).flat_map(|i| (0..c).map(move |j| format!(".acc{i}_{j}(acc{i}_{j})"))))
1130 .collect();
1131 let fold = |rows: std::ops::Range<usize>| {
1132 if rows.is_empty() {
1133 "64'd0".to_string()
1134 } else {
1135 rows.map(|i| format!("rowfold{i}")).collect::<Vec<_>>().join(" ^ ")
1136 }
1137 };
1138 let half = r.div_ceil(2);
1139 let (fold_a, fold_b) = (fold(0..half), fold(half..r));
1140 let row_regs: String = (0..r).map(|i| format!(" reg [63:0] rowfold{i};\n")).collect();
1141 let row_resets: String = (0..r).map(|i| format!(" rowfold{i} <= 64'd0;\n")).collect();
1142 let row_steps: String = (0..r)
1143 .map(|i| {
1144 let row = (0..c).map(|j| format!("acc{i}_{j}")).collect::<Vec<_>>().join(" ^ ");
1145 format!(" rowfold{i} <= {row};\n")
1146 })
1147 .collect();
1148 format!(
1149 "module {core}_realbist(input clk, input rst, output sig);\n\
1150 \x20 reg [63:0] lfsr;\n reg [63:0] misr;\n reg [63:0] foldA;\n reg [63:0] foldB;\n{row_regs}{aw}{wn}{accw}\
1151 \x20 {core} arr(.clk(clk), .rst(rst), {conns});\n\
1152 \x20 always @(posedge clk) begin\n\
1153 \x20 if (rst) begin\n lfsr <= 64'hD1CEF00D12345678;\n misr <= 64'd0;\n foldA <= 64'd0;\n foldB <= 64'd0;\n{row_resets} end else begin\n\
1154 \x20 lfsr <= {{lfsr[62:0], lfsr[63]^lfsr[62]^lfsr[60]^lfsr[59]}};\n\
1155{row_steps}\
1156 \x20 foldA <= {fold_a};\n foldB <= {fold_b};\n\
1157 \x20 misr <= {{misr[62:0], misr[63]}} ^ foldA ^ foldB;\n end\n end\n\
1158 \x20 assign sig = ^misr;\nendmodule\n",
1159 conns = conns.join(", "),
1160 )
1161}
1162
1163pub fn os_streaming_result_offset(period: usize, i: usize, c: usize) -> usize {
1170 period + i + c
1171}
1172
1173pub fn os_streaming_deskew(os: &SystolicOs, j: usize) -> usize {
1179 os.c.saturating_sub(os.k + j)
1180}
1181
1182pub fn emit_systolic_os_streaming_verilog(name: &str, os: &SystolicOs, bits: u32, acc_bits: u32) -> String {
1189 let (r, c) = (os.r, os.c);
1190 let hi = bits.min(64) - 1;
1191 let ow = bits.min(64);
1192 let pw = (2 * bits).min(acc_bits);
1193 let ah = acc_bits.min(64) - 1;
1194 let mut s = String::new();
1195 s.push_str(&format!("module {name}(\n input clk,\n input rst,\n input first,\n"));
1196 for i in 0..r {
1197 s.push_str(&format!(" input signed [63:0] aw{i},\n"));
1198 }
1199 for j in 0..c {
1200 s.push_str(&format!(" input signed [63:0] wn{j},\n"));
1201 }
1202 for i in 0..r {
1203 for j in 0..c {
1204 let comma = if i + 1 == r && j + 1 == c { "" } else { "," };
1205 s.push_str(&format!(" output signed [63:0] res{i}_{j}{comma}\n"));
1206 }
1207 }
1208 s.push_str(");\n");
1209 for i in 0..r {
1210 s.push_str(&format!(" wire signed [{}:0] saw{i} = $signed(aw{i}[{hi}:0]);\n", ow - 1));
1211 }
1212 for j in 0..c {
1213 s.push_str(&format!(" wire signed [{}:0] swn{j} = $signed(wn{j}[{hi}:0]);\n", ow - 1));
1214 }
1215 for i in 0..r {
1217 for d in 0..i {
1218 s.push_str(&format!(" reg signed [{}:0] askew_{i}_{d};\n reg fskew_{i}_{d};\n", ow - 1));
1219 }
1220 }
1221 for j in 0..c {
1222 for d in 0..j {
1223 s.push_str(&format!(" reg signed [{}:0] wskew_{j}_{d};\n", ow - 1));
1224 }
1225 }
1226 for i in 0..r {
1227 if i == 0 {
1228 s.push_str(&format!(" wire signed [{}:0] a_edge0 = saw0;\n wire f_edge0 = first;\n", ow - 1));
1229 } else {
1230 s.push_str(&format!(
1231 " wire signed [{}:0] a_edge{i} = askew_{i}_{d};\n wire f_edge{i} = fskew_{i}_{d};\n",
1232 ow - 1,
1233 d = i - 1
1234 ));
1235 }
1236 }
1237 for j in 0..c {
1238 if j == 0 {
1239 s.push_str(&format!(" wire signed [{}:0] w_edge0 = swn0;\n", ow - 1));
1240 } else {
1241 s.push_str(&format!(" wire signed [{}:0] w_edge{j} = wskew_{j}_{};\n", ow - 1, j - 1));
1242 }
1243 }
1244 for i in 0..r {
1246 for j in 0..c {
1247 s.push_str(&format!(
1248 " reg signed [{o}:0] a_reg_{i}_{j};\n reg signed [{o}:0] w_reg_{i}_{j};\n reg f_reg_{i}_{j};\n reg signed [{ah}:0] acc_reg_{i}_{j};\n reg signed [{ah}:0] res_reg_{i}_{j};\n",
1249 o = ow - 1
1250 ));
1251 let (a_in, f_in) = if j == 0 {
1252 (format!("a_edge{i}"), format!("f_edge{i}"))
1253 } else {
1254 (format!("a_reg_{i}_{}", j - 1), format!("f_reg_{i}_{}", j - 1))
1255 };
1256 let w_in = if i == 0 { format!("w_edge{j}") } else { format!("w_reg_{}_{j}", i - 1) };
1257 s.push_str(&format!(
1258 " wire signed [{o}:0] a_in_{i}_{j} = {a_in};\n wire f_in_{i}_{j} = {f_in};\n wire signed [{o}:0] w_in_{i}_{j} = {w_in};\n wire signed [{}:0] prod_{i}_{j} = a_in_{i}_{j} * w_in_{i}_{j};\n",
1259 pw - 1,
1260 o = ow - 1
1261 ));
1262 let d = os_streaming_deskew(os, j);
1263 for k in 0..d {
1264 s.push_str(&format!(" reg signed [{ah}:0] dres_{i}_{j}_{k};\n"));
1265 }
1266 let src = if d == 0 { format!("res_reg_{i}_{j}") } else { format!("dres_{i}_{j}_{}", d - 1) };
1267 s.push_str(&format!(" assign res{i}_{j} = {src};\n"));
1268 }
1269 }
1270 s.push_str(" always @(posedge clk) begin\n if (rst) begin\n");
1271 for i in 0..r {
1272 for d in 0..i {
1273 s.push_str(&format!(" askew_{i}_{d} <= {ow}'sd0;\n fskew_{i}_{d} <= 1'b0;\n"));
1274 }
1275 }
1276 for j in 0..c {
1277 for d in 0..j {
1278 s.push_str(&format!(" wskew_{j}_{d} <= {ow}'sd0;\n"));
1279 }
1280 }
1281 for i in 0..r {
1282 for j in 0..c {
1283 s.push_str(&format!(
1284 " a_reg_{i}_{j} <= {ow}'sd0;\n w_reg_{i}_{j} <= {ow}'sd0;\n f_reg_{i}_{j} <= 1'b0;\n acc_reg_{i}_{j} <= {a}'sd0;\n res_reg_{i}_{j} <= {a}'sd0;\n",
1285 a = ah + 1
1286 ));
1287 for k in 0..os_streaming_deskew(os, j) {
1288 s.push_str(&format!(" dres_{i}_{j}_{k} <= {a}'sd0;\n", a = ah + 1));
1289 }
1290 }
1291 }
1292 s.push_str(" end else begin\n");
1293 for i in 1..r {
1294 s.push_str(&format!(" askew_{i}_0 <= saw{i};\n fskew_{i}_0 <= first;\n"));
1295 for d in 1..i {
1296 s.push_str(&format!(
1297 " askew_{i}_{d} <= askew_{i}_{p};\n fskew_{i}_{d} <= fskew_{i}_{p};\n",
1298 p = d - 1
1299 ));
1300 }
1301 }
1302 for j in 1..c {
1303 s.push_str(&format!(" wskew_{j}_0 <= swn{j};\n"));
1304 for d in 1..j {
1305 s.push_str(&format!(" wskew_{j}_{d} <= wskew_{j}_{};\n", d - 1));
1306 }
1307 }
1308 for i in 0..r {
1309 for j in 0..c {
1310 s.push_str(&format!(
1311 " a_reg_{i}_{j} <= a_in_{i}_{j};\n w_reg_{i}_{j} <= w_in_{i}_{j};\n f_reg_{i}_{j} <= f_in_{i}_{j};\n\
1312 \x20 if (f_in_{i}_{j}) begin\n res_reg_{i}_{j} <= acc_reg_{i}_{j};\n acc_reg_{i}_{j} <= prod_{i}_{j};\n end else begin\n acc_reg_{i}_{j} <= acc_reg_{i}_{j} + prod_{i}_{j};\n end\n"
1313 ));
1314 let d = os_streaming_deskew(os, j);
1315 if d > 0 {
1316 s.push_str(&format!(" dres_{i}_{j}_0 <= res_reg_{i}_{j};\n"));
1317 for k in 1..d {
1318 s.push_str(&format!(" dres_{i}_{j}_{k} <= dres_{i}_{j}_{};\n", k - 1));
1319 }
1320 }
1321 }
1322 }
1323 s.push_str(" end\n end\nendmodule\n");
1324 s
1325}