Skip to main content

logicaffeine_compile/accel/
cells.rs

1//! Each region cell as a lane routine: the fabric's checked 64-bit semantics
2//! (`AluOp`/`FuncUnit`/`CmpKind::eval_checked`) realized at a device's lane shape by the multi-word core and
3//! the softfloat.
4//!
5//! Every binary cell routine takes its two operands as 64-bit values (the lane words of `a`, then of `b`) and
6//! returns the 64-bit result followed by the fault word (0, or a `Divergence` code). A unary unit ignores
7//! `b`. A select takes the two compared operands and the two arms, and returns the chosen arm.
8
9use std::collections::HashMap;
10use std::sync::{Arc, Mutex, OnceLock};
11
12use serde::{Deserialize, Serialize};
13
14use crate::codegen_sva::fabric_rtl::{AluOp, CmpKind, FuncUnit};
15
16use super::int64::{self, n64, nfault, no_fault};
17use super::lane::{LaneShape, Routine};
18use super::multiword::{Builder, Mw};
19use super::softfloat;
20
21/// What a cell computes.
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
23pub enum CellOp {
24    Alu(AluOp),
25    Unit(FuncUnit),
26    /// An integer select: `cmp(ca, cb) ? t : e`.
27    Select(CmpKind),
28}
29
30impl CellOp {
31    pub fn name(self) -> String {
32        match self {
33            CellOp::Alu(op) => format!("alu_{op:?}").to_lowercase(),
34            CellOp::Unit(u) => format!("unit_{u:?}").to_lowercase(),
35            CellOp::Select(c) => format!("select_{c:?}").to_lowercase(),
36        }
37    }
38
39    /// The 64-bit operands the routine takes.
40    pub fn operands(self) -> usize {
41        match self {
42            CellOp::Select(_) => 4,
43            _ => 2,
44        }
45    }
46
47    /// Whether the routine returns a fault word after its result.
48    pub fn faults(self) -> bool {
49        !matches!(self, CellOp::Select(_))
50    }
51}
52
53fn binary(
54    shape: LaneShape,
55    name: String,
56    f: impl FnOnce(&mut Builder, &Mw, &Mw) -> (Mw, Mw),
57) -> Routine {
58    let mut b = Builder::new(shape);
59    let n = n64(&b);
60    let x = b.mw_input(n);
61    let y = b.mw_input(n);
62    let (v, fault) = f(&mut b, &x, &y);
63    assert_eq!(v.len(), n);
64    assert_eq!(fault.len(), nfault(&b));
65    let inputs = x.0.iter().chain(&y.0).copied().collect();
66    let outputs = v.0.iter().chain(&fault.0).copied().collect();
67    b.finish(name, inputs, outputs)
68}
69
70fn exact(
71    f: impl FnOnce(&mut Builder, &Mw, &Mw) -> Mw,
72) -> impl FnOnce(&mut Builder, &Mw, &Mw) -> (Mw, Mw) {
73    move |b, x, y| {
74        let v = f(b, x, y);
75        let ok = no_fault(b);
76        (v, ok)
77    }
78}
79
80fn predicate(
81    f: impl FnOnce(&mut Builder, &Mw, &Mw) -> u32,
82) -> impl FnOnce(&mut Builder, &Mw, &Mw) -> (Mw, Mw) {
83    move |b, x, y| {
84        let n = x.len();
85        let p = f(b, x, y);
86        let v = int64::bool64(b, p, n);
87        let ok = no_fault(b);
88        (v, ok)
89    }
90}
91
92/// An integer comparison as 0/1.
93fn int_compare(b: &mut Builder, c: CmpKind, x: &Mw, y: &Mw) -> u32 {
94    match c {
95        CmpKind::Lt => b.mw_lts(x, y),
96        CmpKind::Gt => b.mw_lts(y, x),
97        CmpKind::Le => {
98            let gt = b.mw_lts(y, x);
99            b.bit_not(gt)
100        }
101        CmpKind::Ge => {
102            let lt = b.mw_lts(x, y);
103            b.bit_not(lt)
104        }
105        CmpKind::Eq => b.mw_eq(x, y),
106        CmpKind::Ne => {
107            let e = b.mw_eq(x, y);
108            b.bit_not(e)
109        }
110    }
111}
112
113/// Build `op`'s routine at `shape`.
114pub fn build(shape: LaneShape, op: CellOp) -> Routine {
115    let name = op.name();
116    match op {
117        CellOp::Alu(alu) => match alu {
118            AluOp::Add => binary(shape, name, int64::add),
119            AluOp::Sub => binary(shape, name, int64::sub),
120            AluOp::Mul => binary(shape, name, int64::mul),
121            AluOp::And => binary(shape, name, exact(|b, x, y| b.mw_and(x, y))),
122            AluOp::Or => binary(shape, name, exact(|b, x, y| b.mw_or(x, y))),
123            AluOp::Xor => binary(shape, name, exact(|b, x, y| b.mw_xor(x, y))),
124            AluOp::Shl => binary(shape, name, exact(int64::shl)),
125            AluOp::Shr => binary(shape, name, exact(int64::shr)),
126            AluOp::UShr => binary(shape, name, exact(int64::ushr)),
127        },
128        CellOp::Unit(unit) => match unit {
129            FuncUnit::DivS => binary(shape, name, int64::div),
130            FuncUnit::ModS => binary(shape, name, int64::rem),
131            FuncUnit::FloorDivS => binary(shape, name, int64::floor_div),
132            FuncUnit::Pow => binary(shape, name, int64::pow),
133            FuncUnit::MulHiU => binary(shape, name, exact(int64::mul_hi_u)),
134            FuncUnit::MulOvfS => binary(shape, name, exact(int64::mul_ovf_s)),
135            FuncUnit::PopCount => binary(shape, name, exact(|b, x, _| int64::popcount(b, x))),
136            FuncUnit::ApproxEqF => binary(shape, name, predicate(softfloat::approx_eq_ints)),
137            FuncUnit::FAddF => binary(shape, name, exact(|b, x, y| softfloat::add(b, x, y, false))),
138            FuncUnit::FSubF => binary(shape, name, exact(|b, x, y| softfloat::add(b, x, y, true))),
139            FuncUnit::FMulF => binary(shape, name, exact(softfloat::mul)),
140            FuncUnit::FDivF => binary(shape, name, |b, x, y| {
141                let v = softfloat::div(b, x, y);
142                let uy = softfloat::unpack(b, y);
143                let ok = no_fault(b);
144                let f = int64::fault_when(
145                    b,
146                    uy.is_zero,
147                    crate::codegen_sva::fabric_rtl::Divergence::FloatDivideByZero,
148                    &ok,
149                );
150                (v, f)
151            }),
152            FuncUnit::FSqrtF => binary(shape, name, exact(|b, x, _| softfloat::sqrt(b, x))),
153            FuncUnit::FFloorF => {
154                binary(shape, name, exact(|b, x, _| softfloat::floor_to_int(b, x)))
155            }
156            FuncUnit::IntToF => binary(shape, name, exact(|b, x, _| softfloat::from_int(b, x))),
157            FuncUnit::FLtF => binary(
158                shape,
159                name,
160                predicate(|b, x, y| softfloat::compare(b, x, y).0),
161            ),
162            FuncUnit::FGtF => binary(
163                shape,
164                name,
165                predicate(|b, x, y| softfloat::compare(b, y, x).0),
166            ),
167            FuncUnit::FLeF => binary(
168                shape,
169                name,
170                predicate(|b, x, y| softfloat::compare(b, x, y).1),
171            ),
172            FuncUnit::FGeF => binary(
173                shape,
174                name,
175                predicate(|b, x, y| softfloat::compare(b, y, x).1),
176            ),
177            FuncUnit::FEqF => binary(
178                shape,
179                name,
180                predicate(|b, x, y| softfloat::compare(b, x, y).2),
181            ),
182            FuncUnit::FNeF => binary(
183                shape,
184                name,
185                predicate(|b, x, y| {
186                    let e = softfloat::compare(b, x, y).2;
187                    b.bit_not(e)
188                }),
189            ),
190        },
191        CellOp::Select(c) => {
192            let mut b = Builder::new(shape);
193            let n = n64(&b);
194            let ca = b.mw_input(n);
195            let cb = b.mw_input(n);
196            let t = b.mw_input(n);
197            let e = b.mw_input(n);
198            let p = int_compare(&mut b, c, &ca, &cb);
199            let v = b.mw_sel(p, &t, &e);
200            let inputs = [&ca, &cb, &t, &e]
201                .iter()
202                .flat_map(|m| m.0.iter().copied())
203                .collect();
204            b.finish(name, inputs, v.0)
205        }
206    }
207}
208
209/// `op`'s routine at `shape`, built once per process.
210pub fn routine(shape: LaneShape, op: CellOp) -> Arc<Routine> {
211    static CACHE: OnceLock<Mutex<HashMap<(LaneShape, CellOp), Arc<Routine>>>> = OnceLock::new();
212    let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new()));
213    if let Some(r) = cache.lock().expect("the routine cache").get(&(shape, op)) {
214        return r.clone();
215    }
216    let built = Arc::new(build(shape, op));
217    cache
218        .lock()
219        .expect("the routine cache")
220        .entry((shape, op))
221        .or_insert(built)
222        .clone()
223}
224
225/// Every cell op, in a fixed order.
226pub fn all_ops() -> Vec<CellOp> {
227    let alu = [
228        AluOp::Add,
229        AluOp::Sub,
230        AluOp::Mul,
231        AluOp::And,
232        AluOp::Or,
233        AluOp::Xor,
234        AluOp::Shl,
235        AluOp::Shr,
236        AluOp::UShr,
237    ];
238    let units = [
239        FuncUnit::DivS,
240        FuncUnit::ModS,
241        FuncUnit::FloorDivS,
242        FuncUnit::Pow,
243        FuncUnit::MulHiU,
244        FuncUnit::ApproxEqF,
245        FuncUnit::FAddF,
246        FuncUnit::FSubF,
247        FuncUnit::FMulF,
248        FuncUnit::FDivF,
249        FuncUnit::FSqrtF,
250        FuncUnit::FFloorF,
251        FuncUnit::MulOvfS,
252        FuncUnit::IntToF,
253        FuncUnit::FLtF,
254        FuncUnit::FGtF,
255        FuncUnit::FLeF,
256        FuncUnit::FGeF,
257        FuncUnit::FEqF,
258        FuncUnit::FNeF,
259        FuncUnit::PopCount,
260    ];
261    let cmps = [
262        CmpKind::Lt,
263        CmpKind::Gt,
264        CmpKind::Le,
265        CmpKind::Ge,
266        CmpKind::Eq,
267        CmpKind::Ne,
268    ];
269    alu.into_iter()
270        .map(CellOp::Alu)
271        .chain(units.into_iter().map(CellOp::Unit))
272        .chain(cmps.into_iter().map(CellOp::Select))
273        .collect()
274}