Skip to main content

logicaffeine_compile/codegen_sva/
simt_ports.rs

1//! simt_ports — the SIMT engine's step on ports: its uniforms, its mask and every lane's varying as inputs, every
2//! lane's output and the warp-reduce as outputs, a step every clock. Where the engine's register file
3//! ([`emit_gpu_simt_verilog`](super::gpu_simt_rtl::emit_gpu_simt_verilog)) takes a step's operands a slot a clock
4//! and hands its results back a read at a time, this takes them all at once, as a caller holding them in registers
5//! presents them, and reads no register of its own on the way in.
6//!
7//! Each lane of the `a·x + b` kernel computes `a·x` as an aligned tree of adds ([`mul_tree`]), registered after the
8//! levels `cut` names, while `b` and the mask ride registers beside it. The clock after the product, each lane's
9//! masked `a·x + b` is registered (a masked lane yields 0); the clock after that, the warp-reduce of those is
10//! registered and every lane output with it. A step's results are on the outputs `tree latency + 2` clocks after its
11//! operands.
12
13use super::fabric_rtl::AluOp;
14use super::gpu_simt_rtl::{computes_affine_fma, SimtEngine};
15use super::mul_tree::mul_tree;
16
17/// A ports engine's RTL and the clocks from a step's operands to its results.
18#[derive(Clone, Debug, PartialEq)]
19pub struct SimtPorts {
20    pub verilog: String,
21    pub latency: u32,
22}
23
24/// Emit `eng`'s step on ports as module `name`: inputs `clk`, `a`, `b`, `mask` (when the engine is predicated) and
25/// `x0..`, outputs `y0..` and `reduced` (when it reduces). Refused unless the lane kernel computes `a·x + b` with `a`
26/// and `b` uniform and `x` per lane, and its reduce, if any, is an Add, Or, Xor or And: those fold a tree of lanes
27/// to the same word as the engine's fold from the op's identity.
28pub 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;