Skip to main content

logicaffeine_compile/accel/
lane.rs

1//! The lane machine: what a device's integer datapath can actually do, at the width it actually has.
2//!
3//! A device word is `word` bits (32 on a Plasticine PCU or an SN50 lane, two on the analog plane's digit
4//! columns), and its multiplier takes operands of `mul` bits whose product fits one word. Every 64-bit integer
5//! and binary64 operation a region needs is a [`Routine`] over these words, built once by the multi-word core
6//! and the softfloat (`super::multiword`, `super::softfloat`) and executed by whichever [`Lanes`] the device
7//! is: exact digital lanes, or the analog plane under its physics model. No routine assumes a unit the shape
8//! does not declare.
9//!
10//! A routine is a small imperative program: instructions over lane registers, and fixed-count `Repeat`
11//! blocks. Nothing in it branches on data; control that depends on a value is a [`LOp::Sel`], so the same
12//! program is a straight-line datapath on a dataflow chip and a sequencer microprogram on the analog plane's
13//! digital control side.
14
15use serde::{Deserialize, Serialize};
16
17/// The integer datapath a device has: `word`-bit words (a power of two dividing 32), and a multiplier whose
18/// `mul`-bit operands give a product that fits one word (`2 × mul ≤ word`, `mul` dividing `word`).
19#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
20pub struct LaneShape {
21    pub word: u32,
22    pub mul: u32,
23}
24
25impl LaneShape {
26    pub const fn new(word: u32, mul: u32) -> LaneShape {
27        assert!(
28            word >= 2 && word <= 32 && 32 % word == 0,
29            "a lane word is 2, 4, 8, 16 or 32 bits"
30        );
31        assert!(
32            mul >= 1 && 2 * mul <= word && word % mul == 0,
33            "a lane multiplier's product fits one word"
34        );
35        LaneShape { word, mul }
36    }
37
38    /// The mask of one word.
39    pub fn mask(self) -> u32 {
40        if self.word == 32 {
41            u32::MAX
42        } else {
43            (1u32 << self.word) - 1
44        }
45    }
46
47    /// The words that hold `bits` bits.
48    pub fn words_for(self, bits: u32) -> usize {
49        bits.div_ceil(self.word) as usize
50    }
51}
52
53/// A lane register: one word of the device's datapath.
54pub type LReg = u32;
55
56/// One lane operation. Operands are registers; results are words, carries and predicates are 0 or 1.
57#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
58pub enum LOp {
59    /// `dst = k`.
60    Const(u32),
61    /// `dst = a`.
62    Mov(LReg),
63    /// `dst = (a + b + cin) mod 2^w`, `dst2 = (a + b + cin) >> w`; `cin` is 0 or 1.
64    Add {
65        a: LReg,
66        b: LReg,
67        cin: LReg,
68    },
69    /// `dst = (a − b − bin) mod 2^w`, `dst2 = 1` when it borrowed; `bin` is 0 or 1.
70    Sub {
71        a: LReg,
72        b: LReg,
73        bin: LReg,
74    },
75    /// `dst = a × b` for `mul`-bit operands: the exact product, one word.
76    Mul {
77        a: LReg,
78        b: LReg,
79    },
80    And {
81        a: LReg,
82        b: LReg,
83    },
84    Or {
85        a: LReg,
86        b: LReg,
87    },
88    Xor {
89        a: LReg,
90        b: LReg,
91    },
92    /// `dst = !a` within the word.
93    Not {
94        a: LReg,
95    },
96    /// `dst = (a << k) mod 2^w`, `k < w`.
97    Shl {
98        a: LReg,
99        k: u32,
100    },
101    /// `dst = a >> k` (logical), `k < w`.
102    Shr {
103        a: LReg,
104        k: u32,
105    },
106    /// `dst = 1` when `a < b` (unsigned), else 0.
107    Ltu {
108        a: LReg,
109        b: LReg,
110    },
111    /// `dst = 1` when `a == b`, else 0.
112    Eq {
113        a: LReg,
114        b: LReg,
115    },
116    /// `dst = c != 0 ? t : f`.
117    Sel {
118        c: LReg,
119        t: LReg,
120        f: LReg,
121    },
122}
123
124/// An instruction: an operation and the register(s) it writes. `dst2` is the carry/borrow of `Add`/`Sub`.
125#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
126pub struct LInst {
127    pub dst: LReg,
128    pub dst2: Option<LReg>,
129    pub op: LOp,
130}
131
132/// A statement: an instruction, or a body repeated a fixed number of times (registers carry across trips).
133#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
134pub enum LStmt {
135    Inst(LInst),
136    Repeat { times: u32, body: Vec<LStmt> },
137}
138
139/// A lane routine: its inputs and outputs as registers, its body, and the register file size.
140#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
141pub struct Routine {
142    pub name: String,
143    pub shape: LaneShape,
144    pub regs: u32,
145    pub inputs: Vec<LReg>,
146    pub outputs: Vec<LReg>,
147    pub body: Vec<LStmt>,
148}
149
150/// How many of each kind of lane operation a run performed: the cost a device pays for a routine.
151#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
152pub struct LaneCounts {
153    pub add: u64,
154    pub sub: u64,
155    pub mul: u64,
156    pub logic: u64,
157    pub shift: u64,
158    pub compare: u64,
159    pub select: u64,
160    pub moves: u64,
161}
162
163impl LaneCounts {
164    pub fn total(&self) -> u64 {
165        self.add
166            + self.sub
167            + self.mul
168            + self.logic
169            + self.shift
170            + self.compare
171            + self.select
172            + self.moves
173    }
174
175    pub fn absorb(&mut self, o: &LaneCounts) {
176        self.add += o.add;
177        self.sub += o.sub;
178        self.mul += o.mul;
179        self.logic += o.logic;
180        self.shift += o.shift;
181        self.compare += o.compare;
182        self.select += o.select;
183        self.moves += o.moves;
184    }
185}
186
187/// A device's datapath executing lane operations. The arithmetic ones are where devices differ (exact
188/// digital units, or the analog plane settling a column); the rest are the digital side of every device.
189pub trait Lanes {
190    fn shape(&self) -> LaneShape;
191    /// `(a + b + cin) mod 2^w` and its carry.
192    fn add(&mut self, a: u32, b: u32, cin: u32) -> (u32, u32);
193    /// `(a − b − bin) mod 2^w` and its borrow.
194    fn sub(&mut self, a: u32, b: u32, bin: u32) -> (u32, u32);
195    /// The exact product of two `mul`-bit operands.
196    fn mul(&mut self, a: u32, b: u32) -> u32;
197}
198
199/// Exact digital lanes: the units a PCU or an RDU lane has, at their declared width.
200#[derive(Clone, Copy, Debug)]
201pub struct ExactLanes {
202    pub shape: LaneShape,
203}
204
205impl Lanes for ExactLanes {
206    fn shape(&self) -> LaneShape {
207        self.shape
208    }
209    fn add(&mut self, a: u32, b: u32, cin: u32) -> (u32, u32) {
210        let s = a as u64 + b as u64 + cin as u64;
211        (
212            (s & self.shape.mask() as u64) as u32,
213            (s >> self.shape.word) as u32,
214        )
215    }
216    fn sub(&mut self, a: u32, b: u32, bin: u32) -> (u32, u32) {
217        let d = a as i64 - b as i64 - bin as i64;
218        let borrow = u32::from(d < 0);
219        ((d.rem_euclid(1i64 << self.shape.word)) as u32, borrow)
220    }
221    fn mul(&mut self, a: u32, b: u32) -> u32 {
222        a * b
223    }
224}
225
226/// Why a routine could not run: a builder bug the interpreter refuses to paper over.
227#[derive(Clone, Debug, PartialEq, Eq)]
228pub enum LaneFault {
229    /// A word wider than the shape reached an operation.
230    WideWord { op: &'static str, value: u32 },
231    /// A multiplier operand wider than `mul` bits.
232    WideMultiplicand { value: u32 },
233    /// A carry-in or borrow-in that is not 0 or 1.
234    NotABit { value: u32 },
235    /// The routine was given the wrong number of input words.
236    Arity { expected: usize, got: usize },
237}
238
239impl std::fmt::Display for LaneFault {
240    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241        match self {
242            LaneFault::WideWord { op, value } => {
243                write!(f, "{op}: operand {value:#x} is wider than the lane word")
244            }
245            LaneFault::WideMultiplicand { value } => {
246                write!(f, "mul: operand {value:#x} is wider than the multiplier")
247            }
248            LaneFault::NotABit { value } => write!(f, "carry {value} is not a bit"),
249            LaneFault::Arity { expected, got } => {
250                write!(f, "{got} input words where the routine takes {expected}")
251            }
252        }
253    }
254}
255
256impl std::error::Error for LaneFault {}
257
258/// Run `routine` on `lanes` with `inputs` (one word per input register); the output words, and what it cost.
259pub fn run(
260    routine: &Routine,
261    lanes: &mut dyn Lanes,
262    inputs: &[u32],
263) -> Result<(Vec<u32>, LaneCounts), LaneFault> {
264    if inputs.len() != routine.inputs.len() {
265        return Err(LaneFault::Arity {
266            expected: routine.inputs.len(),
267            got: inputs.len(),
268        });
269    }
270    let shape = lanes.shape();
271    let mut regs = vec![0u32; routine.regs as usize];
272    for (r, v) in routine.inputs.iter().zip(inputs) {
273        if *v & !shape.mask() != 0 {
274            return Err(LaneFault::WideWord {
275                op: "input",
276                value: *v,
277            });
278        }
279        regs[*r as usize] = *v;
280    }
281    let mut counts = LaneCounts::default();
282    exec(&routine.body, &mut regs, lanes, shape, &mut counts)?;
283    Ok((
284        routine.outputs.iter().map(|r| regs[*r as usize]).collect(),
285        counts,
286    ))
287}
288
289fn exec(
290    body: &[LStmt],
291    regs: &mut [u32],
292    lanes: &mut dyn Lanes,
293    shape: LaneShape,
294    counts: &mut LaneCounts,
295) -> Result<(), LaneFault> {
296    for stmt in body {
297        match stmt {
298            LStmt::Repeat { times, body } => {
299                for _ in 0..*times {
300                    exec(body, regs, lanes, shape, counts)?;
301                }
302            }
303            LStmt::Inst(inst) => step(inst, regs, lanes, shape, counts)?,
304        }
305    }
306    Ok(())
307}
308
309fn step(
310    inst: &LInst,
311    regs: &mut [u32],
312    lanes: &mut dyn Lanes,
313    shape: LaneShape,
314    counts: &mut LaneCounts,
315) -> Result<(), LaneFault> {
316    let mask = shape.mask();
317    let r = |x: LReg| regs[x as usize];
318    let bit = |v: u32| {
319        if v > 1 {
320            Err(LaneFault::NotABit { value: v })
321        } else {
322            Ok(v)
323        }
324    };
325    let (v, v2) = match inst.op {
326        LOp::Const(k) => {
327            if k & !mask != 0 {
328                return Err(LaneFault::WideWord {
329                    op: "const",
330                    value: k,
331                });
332            }
333            counts.moves += 1;
334            (k, None)
335        }
336        LOp::Mov(a) => {
337            counts.moves += 1;
338            (r(a), None)
339        }
340        LOp::Add { a, b, cin } => {
341            counts.add += 1;
342            let (s, c) = lanes.add(r(a), r(b), bit(r(cin))?);
343            (s, Some(c))
344        }
345        LOp::Sub { a, b, bin } => {
346            counts.sub += 1;
347            let (d, bo) = lanes.sub(r(a), r(b), bit(r(bin))?);
348            (d, Some(bo))
349        }
350        LOp::Mul { a, b } => {
351            counts.mul += 1;
352            let limit = 1u32 << shape.mul;
353            for x in [r(a), r(b)] {
354                if x >= limit {
355                    return Err(LaneFault::WideMultiplicand { value: x });
356                }
357            }
358            (lanes.mul(r(a), r(b)), None)
359        }
360        LOp::And { a, b } => {
361            counts.logic += 1;
362            (r(a) & r(b), None)
363        }
364        LOp::Or { a, b } => {
365            counts.logic += 1;
366            (r(a) | r(b), None)
367        }
368        LOp::Xor { a, b } => {
369            counts.logic += 1;
370            (r(a) ^ r(b), None)
371        }
372        LOp::Not { a } => {
373            counts.logic += 1;
374            (!r(a) & mask, None)
375        }
376        LOp::Shl { a, k } => {
377            counts.shift += 1;
378            ((r(a) << k) & mask, None)
379        }
380        LOp::Shr { a, k } => {
381            counts.shift += 1;
382            (r(a) >> k, None)
383        }
384        LOp::Ltu { a, b } => {
385            counts.compare += 1;
386            (u32::from(r(a) < r(b)), None)
387        }
388        LOp::Eq { a, b } => {
389            counts.compare += 1;
390            (u32::from(r(a) == r(b)), None)
391        }
392        LOp::Sel { c, t, f } => {
393            counts.select += 1;
394            (if r(c) != 0 { r(t) } else { r(f) }, None)
395        }
396    };
397    regs[inst.dst as usize] = v;
398    if let (Some(d2), Some(x)) = (inst.dst2, v2) {
399        regs[d2 as usize] = x;
400    }
401    Ok(())
402}
403
404/// Split a 64-bit value into `n` little-endian words of `shape.word` bits.
405pub fn split_u64(shape: LaneShape, x: u64, n: usize) -> Vec<u32> {
406    (0..n)
407        .map(|i| {
408            let at = i as u32 * shape.word;
409            if at >= 64 {
410                0
411            } else {
412                ((x >> at) as u32) & shape.mask()
413            }
414        })
415        .collect()
416}
417
418/// Join little-endian words back into a 64-bit value (words past bit 63 are ignored).
419pub fn join_u64(shape: LaneShape, words: &[u32]) -> u64 {
420    let mut x = 0u64;
421    for (i, w) in words.iter().enumerate() {
422        let at = i as u32 * shape.word;
423        if at < 64 {
424            x |= (*w as u64) << at;
425        }
426    }
427    x
428}
429
430/// Split a 128-bit value into `n` little-endian words.
431pub fn split_u128(shape: LaneShape, x: u128, n: usize) -> Vec<u32> {
432    (0..n)
433        .map(|i| {
434            let at = i as u32 * shape.word;
435            if at >= 128 {
436                0
437            } else {
438                ((x >> at) as u32) & shape.mask()
439            }
440        })
441        .collect()
442}
443
444/// Join little-endian words into a 128-bit value.
445pub fn join_u128(shape: LaneShape, words: &[u32]) -> u128 {
446    let mut x = 0u128;
447    for (i, w) in words.iter().enumerate() {
448        let at = i as u32 * shape.word;
449        if at < 128 {
450            x |= (*w as u128) << at;
451        }
452    }
453    x
454}