logicaffeine_compile/codegen_sva/
simt_ports.rs1use super::fabric_rtl::AluOp;
14use super::gpu_simt_rtl::{computes_affine_fma, SimtEngine};
15use super::mul_tree::mul_tree;
16
17#[derive(Clone, Debug, PartialEq)]
19pub struct SimtPorts {
20 pub verilog: String,
21 pub latency: u32,
22}
23
24pub fn emit_simt_ports_verilog(name: &str, eng: &SimtEngine, cut: impl Fn(usize) -> bool) -> Result<SimtPorts, String> {
29 if !computes_affine_fma(eng) {
30 return Err(format!("{name}: the ports engine computes the a·x + b kernel; this one computes something else"));
31 }
32 if eng.uniform != [true, false, true] {
33 return Err(format!("{name}: a and b are uniform and x is per lane, not {:?}", eng.uniform));
34 }
35 let fold = match eng.reduce {
36 None => None,
37 Some(op @ (AluOp::Add | AluOp::Or | AluOp::Xor | AluOp::And)) => Some(op),
38 Some(op) => return Err(format!("{name}: a warp-reduce of {op:?} does not fold as a tree of lanes")),
39 };
40 let n = eng.n_lanes;
41 let trees: Vec<_> = (0..n).map(|j| mul_tree(&format!("m{j}"), "a", &format!("x{j}"), 64, &cut)).collect();
42 let lat = trees[0].latency as usize;
43
44 let mut ports = vec!["input clk".to_string(), "input [63:0] a".to_string(), "input [63:0] b".to_string()];
45 if eng.predicated {
46 ports.push(format!("input [{}:0] mask", n - 1));
47 }
48 ports.extend((0..n).map(|j| format!("input [63:0] x{j}")));
49 ports.extend((0..n).map(|j| format!("output reg [63:0] y{j}")));
50 if fold.is_some() {
51 ports.push("output reg [63:0] reduced".to_string());
52 }
53
54 let mut decls = String::new();
55 let mut moves = String::new();
56 for t in &trees {
57 for (r, bits) in &t.regs {
58 decls.push_str(&format!(" reg [{}:0] {r};\n", bits - 1));
59 }
60 for (r, v) in &t.moves {
61 moves.push_str(&format!(" {r} <= {v};\n"));
62 }
63 }
64 for d in 1..=lat {
65 decls.push_str(&format!(" reg [63:0] b_d{d};\n"));
66 moves.push_str(&format!(" b_d{d} <= {};\n", if d == 1 { "b".to_string() } else { format!("b_d{}", d - 1) }));
67 if eng.predicated {
68 decls.push_str(&format!(" reg [{}:0] mask_d{d};\n", n - 1));
69 moves.push_str(&format!(
70 " mask_d{d} <= {};\n",
71 if d == 1 { "mask".to_string() } else { format!("mask_d{}", d - 1) }
72 ));
73 }
74 }
75 let b_at = if lat == 0 { "b".to_string() } else { format!("b_d{lat}") };
76 let mask_at = if lat == 0 { "mask".to_string() } else { format!("mask_d{lat}") };
77 let mut wires = String::new();
78 for (j, t) in trees.iter().enumerate() {
79 wires.push_str(&t.wires);
80 decls.push_str(&format!(" reg [63:0] lane{j};\n"));
81 let k = format!("{} + {b_at}", t.product);
82 let kept = if eng.predicated { format!("{mask_at}[{j}] ? {k} : 64'd0") } else { k };
83 moves.push_str(&format!(" lane{j} <= {kept};\n"));
84 moves.push_str(&format!(" y{j} <= lane{j};\n"));
85 }
86 if let Some(op) = fold {
87 let sep = match op {
88 AluOp::Add => " + ",
89 AluOp::Or => " | ",
90 AluOp::Xor => " ^ ",
91 _ => " & ",
92 };
93 let lanes: Vec<String> = (0..n).map(|j| format!("lane{j}")).collect();
94 moves.push_str(&format!(" reduced <= {};\n", lanes.join(sep)));
95 }
96 let verilog = format!(
97 "// {name} — the SIMT engine's step on ports ({n} lanes of a·x + b{masked}{reduce}): every operand in at once,\n\
98 // each lane's a·x an aligned tree of adds {lat} clock(s) deep, its masked a·x + b registered, then the lane\n\
99 // outputs and the warp-reduce registered together — a step every clock, its results {latency} clocks later.\n\
100 module {name}({ports});\n\
101 {decls}{wires} always @(posedge clk) begin\n\
102 {moves} end\n\
103 endmodule\n",
104 masked = if eng.predicated { ", masked" } else { "" },
105 reduce = fold.map_or(String::new(), |op| format!(", {op:?}-reduced")),
106 latency = lat + 2,
107 ports = ports.join(", "),
108 );
109 Ok(SimtPorts { verilog, latency: lat as u32 + 2 })
110}
111
112#[cfg(test)]
113#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/simt_ports/tests.rs"]
114mod tests;