Skip to main content

logicaffeine_compile/codegen_ana/
place.rs

1//! Non-ideality-aware placement — operand adjacency as a matching resource.
2//!
3//! The Pelgrom distance gradient makes a tile's gain error grow with its operand separation,
4//! so placement is not cosmetic: a sensitivity-blind scatter of a deep chain accumulates
5//! distance-gain error the budget must then carry. The aware placer greedily seats each tile
6//! at the free site minimizing pitch distance to its operand tiles (deterministic tie-break:
7//! row-major). `place_blind` is the deliberate straw man — maximum-stride scatter — kept
8//! public so the anti-vacuity lock can prove the cost function earns its keep.
9
10use super::cell::{ASrc, AnalogCell, AnalogNetlist};
11use super::target::{AnaUnplaceable, AnalogTarget};
12use super::AnaLowerError;
13
14/// Per-cell site assignment on the target's tile array.
15#[derive(Clone, Debug, PartialEq)]
16pub struct AnalogPlacement {
17    pub site: Vec<(usize, usize)>,
18}
19
20impl AnalogPlacement {
21    /// Manhattan pitch distance between two placed cells.
22    pub fn pitch_distance(&self, a: usize, b: usize) -> f64 {
23        let (ar, ac) = self.site[a];
24        let (br, bc) = self.site[b];
25        (ar.abs_diff(br) + ac.abs_diff(bc)) as f64
26    }
27
28    /// Total operand-distance cost (the objective the aware placer minimizes).
29    pub fn wire_cost(&self, netlist: &AnalogNetlist) -> f64 {
30        let mut cost = 0.0;
31        for (i, cell) in netlist.cells.iter().enumerate() {
32            let mut add = |s: &ASrc| {
33                if let ASrc::Val(v) = s {
34                    cost += self.pitch_distance(i, *v);
35                }
36            };
37            match cell {
38                AnalogCell::Sum { inputs, .. } => inputs.iter().for_each(&mut add),
39                AnalogCell::Mul { a, b } => {
40                    add(a);
41                    add(b);
42                }
43                AnalogCell::GainConst { a, .. } => add(a),
44                AnalogCell::Select { c, t, f } => {
45                    add(c);
46                    add(t);
47                    add(f);
48                }
49                AnalogCell::Exp { a }
50                | AnalogCell::Log { a }
51                | AnalogCell::Tanh { a }
52                | AnalogCell::Sqrt { a } => add(a),
53                AnalogCell::Div { a, b } => {
54                    add(a);
55                    add(b);
56                }
57            }
58        }
59        cost
60    }
61}
62
63/// Greedy operand-adjacent placement: seat cells in SSA order, each at the free site with the
64/// smallest total pitch distance to its already-placed operands (row-major tie-break —
65/// deterministic, reproducible).
66pub fn place_analog(
67    netlist: &AnalogNetlist,
68    target: &AnalogTarget,
69) -> Result<AnalogPlacement, AnaLowerError> {
70    let sites_total = target.rows * target.cols;
71    if netlist.cells.len() > sites_total {
72        return Err(AnaLowerError::Unplaceable(AnaUnplaceable::TooSmall {
73            needed_cells: netlist.cells.len(),
74            target_rows: target.rows,
75            target_cols: target.cols,
76            target: target.name,
77        }));
78    }
79    let mut used = vec![false; sites_total];
80    let mut site: Vec<(usize, usize)> = Vec::with_capacity(netlist.cells.len());
81    for (i, cell) in netlist.cells.iter().enumerate() {
82        let operands: Vec<usize> = operand_cells(cell);
83        let mut best: Option<(f64, usize)> = None;
84        for s in 0..sites_total {
85            if used[s] {
86                continue;
87            }
88            let (r, c) = (s / target.cols, s % target.cols);
89            let d: f64 = operands
90                .iter()
91                .map(|o| {
92                    let (or, oc) = site[*o];
93                    (r.abs_diff(or) + c.abs_diff(oc)) as f64
94                })
95                .sum();
96            if best.map(|(bd, _)| d < bd).unwrap_or(true) {
97                best = Some((d, s));
98            }
99        }
100        let (_, s) = best.expect("a free site exists (counted above)");
101        used[s] = true;
102        site.push((s / target.cols, s % target.cols));
103        let _ = i;
104    }
105    Ok(AnalogPlacement { site })
106}
107
108/// The deliberate straw man: maximum-stride scatter (successive cells thrown to opposite
109/// corners), used by the anti-vacuity lock to prove aware placement pays.
110pub fn place_blind(
111    netlist: &AnalogNetlist,
112    target: &AnalogTarget,
113) -> Result<AnalogPlacement, AnaLowerError> {
114    let sites_total = target.rows * target.cols;
115    if netlist.cells.len() > sites_total {
116        return Err(AnaLowerError::Unplaceable(AnaUnplaceable::TooSmall {
117            needed_cells: netlist.cells.len(),
118            target_rows: target.rows,
119            target_cols: target.cols,
120            target: target.name,
121        }));
122    }
123    // Walk sites with a large coprime stride so consecutive cells land far apart.
124    let stride = (sites_total / 2).max(1) | 1;
125    let mut used = vec![false; sites_total];
126    let mut cursor = 0usize;
127    let mut site = Vec::with_capacity(netlist.cells.len());
128    for _ in &netlist.cells {
129        while used[cursor] {
130            cursor = (cursor + 1) % sites_total;
131        }
132        used[cursor] = true;
133        site.push((cursor / target.cols, cursor % target.cols));
134        cursor = (cursor + stride) % sites_total;
135    }
136    Ok(AnalogPlacement { site })
137}
138
139fn operand_cells(cell: &AnalogCell) -> Vec<usize> {
140    let mut out = Vec::new();
141    let mut add = |s: &ASrc| {
142        if let ASrc::Val(v) = s {
143            out.push(*v);
144        }
145    };
146    match cell {
147        AnalogCell::Sum { inputs, .. } => inputs.iter().for_each(&mut add),
148        AnalogCell::Mul { a, b } => {
149            add(a);
150            add(b);
151        }
152        AnalogCell::GainConst { a, .. } => add(a),
153        AnalogCell::Select { c, t, f } => {
154            add(c);
155            add(t);
156            add(f);
157        }
158        AnalogCell::Exp { a }
159        | AnalogCell::Log { a }
160        | AnalogCell::Tanh { a }
161        | AnalogCell::Sqrt { a } => add(a),
162        AnalogCell::Div { a, b } => {
163            add(a);
164            add(b);
165        }
166    }
167    out
168}