1use super::fabric_rtl::{AluOp, CmpKind, DecisionFabric, LoopCell, Src};
25use std::collections::{BTreeMap, BTreeSet, HashMap};
26
27type Id = usize;
28
29pub const HOIST_NODE_LIMIT: usize = 1 << 16;
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
35enum Pred {
36 Lt(Id, Id),
37 Eq(Id, Id),
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
41enum Node {
42 In(usize),
43 Const(i64),
44 Arith(AluOp, Id, Id),
45 Sel(Pred, Id, Id),
46}
47
48enum Built {
50 Open(Pred),
51 Decided(bool),
52}
53
54struct Graph {
55 nodes: Vec<Node>,
56 index: HashMap<Node, Id>,
57 width: u32,
58}
59
60impl Graph {
61 fn add(&mut self, n: Node) -> Result<Id, String> {
62 if let Some(&id) = self.index.get(&n) {
63 return Ok(id);
64 }
65 if self.nodes.len() >= HOIST_NODE_LIMIT {
66 return Err(format!("hoisting outgrows {HOIST_NODE_LIMIT} nodes: its predicates do not collapse"));
67 }
68 self.nodes.push(n);
69 self.index.insert(n, self.nodes.len() - 1);
70 Ok(self.nodes.len() - 1)
71 }
72
73 fn word(&self, c: i64) -> i64 {
75 let shift = 64 - self.width;
76 (c << shift) >> shift
77 }
78
79 fn konst(&mut self, c: i64) -> Result<Id, String> {
80 let c = self.word(c);
81 self.add(Node::Const(c))
82 }
83
84 fn value(&self, id: Id) -> Option<i64> {
85 match self.nodes[id] {
86 Node::Const(c) => Some(c),
87 _ => None,
88 }
89 }
90
91 fn arith(&mut self, op: AluOp, a: Id, b: Id) -> Result<Id, String> {
94 use AluOp::*;
95 let low_bits = matches!(op, Add | Sub | Mul | And | Or | Xor);
96 match (op, self.value(a), self.value(b)) {
97 (_, Some(x), Some(y)) if low_bits => return self.konst(op.eval(x, y)),
98 (And, Some(0), _) | (And, _, Some(0)) | (Mul, Some(0), _) | (Mul, _, Some(0)) => return self.konst(0),
99 (And, Some(-1), _) | (Or | Xor | Add, Some(0), _) | (Mul, Some(1), _) => return Ok(b),
100 (And, _, Some(-1)) | (Or | Xor | Add | Sub, _, Some(0)) | (Mul, _, Some(1)) => return Ok(a),
101 (Shl | Shr | UShr, _, Some(0)) => return Ok(a),
102 _ => {}
103 }
104 let negated = |id: Id| match self.nodes[id] {
107 Node::Arith(Sub, z, y) if self.value(z) == Some(0) => Some(y),
108 _ => None,
109 };
110 match (op, negated(a), negated(b)) {
111 (Add, _, Some(y)) => return self.arith(Sub, a, y),
112 (Add, Some(y), None) => return self.arith(Sub, b, y),
113 (Sub, _, Some(y)) => return self.arith(Add, a, y),
114 _ => {}
115 }
116 let (a, b) = if matches!(op, Add | Mul | And | Or | Xor) && b < a { (b, a) } else { (a, b) };
117 self.add(Node::Arith(op, a, b))
118 }
119
120 fn compare(&mut self, cmp: CmpKind, x: Id, y: Id) -> (Built, bool) {
123 let (p, polarity) = match cmp {
124 CmpKind::Lt => (Pred::Lt(x, y), true),
125 CmpKind::Ge => (Pred::Lt(x, y), false),
126 CmpKind::Gt => (Pred::Lt(y, x), true),
127 CmpKind::Le => (Pred::Lt(y, x), false),
128 CmpKind::Eq => (Pred::Eq(x.min(y), x.max(y)), true),
129 CmpKind::Ne => (Pred::Eq(x.min(y), x.max(y)), false),
130 };
131 (self.decide_constant(p), polarity)
132 }
133
134 fn decide_constant(&self, p: Pred) -> Built {
135 match p {
136 Pred::Lt(x, y) if x == y => Built::Decided(false),
137 Pred::Eq(x, y) if x == y => Built::Decided(true),
138 Pred::Lt(x, y) => match (self.value(x), self.value(y)) {
139 (Some(a), Some(b)) => Built::Decided(a < b),
140 _ => Built::Open(p),
141 },
142 Pred::Eq(x, y) => match (self.value(x), self.value(y)) {
143 (Some(a), Some(b)) => Built::Decided(a == b),
144 _ => Built::Open(p),
145 },
146 }
147 }
148
149 fn sel(&mut self, p: Pred, t: Id, e: Id) -> Result<Id, String> {
150 if t == e {
151 return Ok(t);
152 }
153 self.add(Node::Sel(p, t, e))
154 }
155
156 fn restrict(&mut self, id: Id, env: &BTreeMap<Pred, bool>, memo: &mut HashMap<Id, Id>) -> Result<Id, String> {
159 if let Some(&r) = memo.get(&id) {
160 return Ok(r);
161 }
162 let r = match self.nodes[id] {
163 Node::In(_) | Node::Const(_) => id,
164 Node::Arith(op, a, b) => {
165 let (a, b) = (self.restrict(a, env, memo)?, self.restrict(b, env, memo)?);
166 self.arith(op, a, b)?
167 }
168 Node::Sel(p, t, e) => {
169 let built = match p {
170 Pred::Lt(x, y) => {
171 let (x, y) = (self.restrict(x, env, memo)?, self.restrict(y, env, memo)?);
172 self.decide_constant(Pred::Lt(x, y))
173 }
174 Pred::Eq(x, y) => {
175 let (x, y) = (self.restrict(x, env, memo)?, self.restrict(y, env, memo)?);
176 self.decide_constant(Pred::Eq(x.min(y), x.max(y)))
177 }
178 };
179 let known = match built {
180 Built::Decided(v) => Ok(v),
181 Built::Open(q) => decided(env, q).ok_or(q),
182 };
183 match known {
184 Ok(true) => self.restrict(t, env, memo)?,
185 Ok(false) => self.restrict(e, env, memo)?,
186 Err(q) => {
187 let (t, e) = (self.restrict(t, env, memo)?, self.restrict(e, env, memo)?);
188 self.sel(q, t, e)?
189 }
190 }
191 }
192 };
193 memo.insert(id, r);
194 Ok(r)
195 }
196
197 fn cone(&self, root: Id) -> BTreeSet<Id> {
200 let mut seen = BTreeSet::new();
201 let mut stack = vec![root];
202 while let Some(id) = stack.pop() {
203 if !seen.insert(id) {
204 continue;
205 }
206 match self.nodes[id] {
207 Node::In(_) | Node::Const(_) => {}
208 Node::Arith(_, a, b) => stack.extend([a, b]),
209 Node::Sel(p, t, e) => {
210 let (x, y) = operands(p);
211 stack.extend([x, y, t, e]);
212 }
213 }
214 }
215 seen
216 }
217
218 fn first_blocking(&self, root: Id) -> Option<Pred> {
221 let cone = self.cone(root);
222 let is_sel = |id: Id| matches!(self.nodes[id], Node::Sel(..));
223 let mut blocking = BTreeSet::new();
224 for &id in &cone {
225 match self.nodes[id] {
226 Node::Arith(_, a, b) => blocking.extend([a, b].into_iter().filter(|&s| is_sel(s))),
227 Node::Sel(p, _, _) => {
228 let (x, y) = operands(p);
229 blocking.extend([x, y].into_iter().filter(|&s| is_sel(s)));
230 }
231 Node::In(_) | Node::Const(_) => {}
232 }
233 }
234 blocking.into_iter().next().map(|s| match self.nodes[s] {
235 Node::Sel(p, _, _) => p,
236 _ => unreachable!("only selects block"),
237 })
238 }
239
240 fn expand(&mut self, root: Id, env: &mut BTreeMap<Pred, bool>) -> Result<Id, String> {
242 let r = self.restrict(root, env, &mut HashMap::new())?;
243 let Some(p) = self.first_blocking(r) else {
244 return Ok(r);
245 };
246 env.insert(p, true);
247 let t = self.expand(r, env);
248 env.insert(p, false);
249 let e = self.expand(r, env);
250 env.remove(&p);
251 self.sel(p, t?, e?)
252 }
253
254 fn arrival(&self, id: Id, memo: &mut HashMap<Id, f64>) -> f64 {
257 if let Some(&a) = memo.get(&id) {
258 return a;
259 }
260 let a = match self.nodes[id] {
261 Node::In(_) | Node::Const(_) => 0.0,
262 Node::Arith(op, a, b) => {
263 let wiring = matches!(op, AluOp::Shl | AluOp::Shr | AluOp::UShr) && self.value(b).is_some()
264 || matches!(op, AluOp::And | AluOp::Or | AluOp::Xor)
265 && (self.value(a).is_some() || self.value(b).is_some());
266 let d = if wiring {
267 0.0
268 } else {
269 match op {
270 AluOp::Add | AluOp::Sub => 9.0,
271 AluOp::Mul => 40.0,
272 AluOp::And | AluOp::Or => 0.6,
273 AluOp::Xor => 1.0,
274 AluOp::Shl | AluOp::Shr | AluOp::UShr => 4.0,
275 }
276 };
277 self.arrival(a, memo).max(self.arrival(b, memo)) + d
278 }
279 Node::Sel(p, t, e) => {
280 let ready = self.pred_arrival(p, memo);
281 ready.max(self.arrival(t, memo)).max(self.arrival(e, memo)) + 2.6
282 }
283 };
284 memo.insert(id, a);
285 a
286 }
287
288 fn pred_arrival(&self, p: Pred, memo: &mut HashMap<Id, f64>) -> f64 {
289 let (x, y) = operands(p);
290 let ready = self.arrival(x, memo).max(self.arrival(y, memo));
291 ready
292 + match p {
293 Pred::Lt(..) => 9.0 + 2.6,
294 Pred::Eq(..) => 1.0 + 6.0 * 0.6,
295 }
296 }
297
298 fn reorder(&mut self, root: Id, order: &[Pred], env: &mut BTreeMap<Pred, bool>) -> Result<Id, String> {
301 let r = self.restrict(root, env, &mut HashMap::new())?;
302 let present: BTreeSet<Pred> = self
303 .cone(r)
304 .into_iter()
305 .filter_map(|id| match self.nodes[id] {
306 Node::Sel(p, _, _) => Some(p),
307 _ => None,
308 })
309 .collect();
310 let Some(&p) = order.iter().find(|p| present.contains(p)) else {
311 return Ok(r);
312 };
313 env.insert(p, true);
314 let t = self.reorder(r, order, env);
315 env.insert(p, false);
316 let e = self.reorder(r, order, env);
317 env.remove(&p);
318 self.sel(p, t?, e?)
319 }
320}
321
322fn operands(p: Pred) -> (Id, Id) {
323 match p {
324 Pred::Lt(x, y) | Pred::Eq(x, y) => (x, y),
325 }
326}
327
328fn decided(env: &BTreeMap<Pred, bool>, p: Pred) -> Option<bool> {
331 if let Some(&v) = env.get(&p) {
332 return Some(v);
333 }
334 let eq = |x: Id, y: Id| Pred::Eq(x.min(y), x.max(y));
335 match p {
336 Pred::Lt(x, y) => match (env.get(&Pred::Lt(y, x)), env.get(&eq(x, y))) {
337 (Some(true), _) | (_, Some(true)) => Some(false),
338 (Some(false), Some(false)) => Some(true),
339 _ => None,
340 },
341 Pred::Eq(x, y) => match (env.get(&Pred::Lt(x, y)), env.get(&Pred::Lt(y, x))) {
342 (Some(true), _) | (_, Some(true)) => Some(false),
343 (Some(false), Some(false)) => Some(true),
344 _ => None,
345 },
346 }
347}
348
349pub fn hoist_selects(df: &DecisionFabric, width: u32) -> Result<DecisionFabric, String> {
354 assert!((2..=64).contains(&width), "the datapath width is 2..=64 bits, not {width}");
355 let mut g = Graph { nodes: Vec::new(), index: HashMap::new(), width };
356 let mut vals: Vec<Id> = Vec::with_capacity(df.cells.len());
357 let node = |g: &mut Graph, s: Src, vals: &[Id]| -> Result<Id, String> {
358 match s {
359 Src::In(k) => g.add(Node::In(k)),
360 Src::Const(c) => g.konst(c),
361 Src::Val(k) => Ok(vals[k]),
362 }
363 };
364 for (i, cell) in df.cells.iter().enumerate() {
365 let id = match *cell {
366 LoopCell::Arith { op, a, b } => {
367 let (a, b) = (node(&mut g, a, &vals)?, node(&mut g, b, &vals)?);
368 g.arith(op, a, b)?
369 }
370 LoopCell::Select { cmp, ca, cb, t, e } => {
371 let (x, y) = (node(&mut g, ca, &vals)?, node(&mut g, cb, &vals)?);
372 let (t, e) = (node(&mut g, t, &vals)?, node(&mut g, e, &vals)?);
373 match g.compare(cmp, x, y) {
374 (Built::Decided(v), polarity) => if v == polarity { t } else { e },
375 (Built::Open(p), true) => g.sel(p, t, e)?,
376 (Built::Open(p), false) => g.sel(p, e, t)?,
377 }
378 }
379 ref other => return Err(format!("cell {i} ({other:?}) has no combinational form to hoist")),
380 };
381 vals.push(id);
382 }
383 let root = node(&mut g, df.result, &vals)?;
384 let expanded = g.expand(root, &mut BTreeMap::new())?;
385
386 let mut memo = HashMap::new();
387 let mut order: Vec<(f64, Pred)> = g
388 .cone(expanded)
389 .into_iter()
390 .filter_map(|id| match g.nodes[id] {
391 Node::Sel(p, _, _) => Some(p),
392 _ => None,
393 })
394 .collect::<BTreeSet<Pred>>()
395 .into_iter()
396 .map(|p| (g.pred_arrival(p, &mut memo), p))
397 .collect();
398 order.sort_by(|a, b| b.0.total_cmp(&a.0).then(a.1.cmp(&b.1)));
399 let order: Vec<Pred> = order.into_iter().map(|(_, p)| p).collect();
400 let tree = g.reorder(expanded, &order, &mut BTreeMap::new())?;
401
402 let mut cells = Vec::new();
403 let mut src_of: HashMap<Id, Src> = HashMap::new();
404 for id in g.cone(tree) {
405 let src = |n: Id| src_of[&n];
406 let s = match g.nodes[id] {
407 Node::In(k) => Src::In(k),
408 Node::Const(c) => Src::Const(c),
409 Node::Arith(op, a, b) => {
410 cells.push(LoopCell::Arith { op, a: src(a), b: src(b) });
411 Src::Val(cells.len() - 1)
412 }
413 Node::Sel(p, t, e) => {
414 let (cmp, (x, y)) = match p {
415 Pred::Lt(..) => (CmpKind::Lt, operands(p)),
416 Pred::Eq(..) => (CmpKind::Eq, operands(p)),
417 };
418 cells.push(LoopCell::Select { cmp, ca: src(x), cb: src(y), t: src(t), e: src(e) });
419 Src::Val(cells.len() - 1)
420 }
421 };
422 src_of.insert(id, s);
423 }
424 Ok(DecisionFabric { n_in: df.n_in, cells, result: src_of[&tree], escaped_first_return: df.escaped_first_return })
425}
426
427pub fn reads_a_select(df: &DecisionFabric) -> bool {
429 let is_select = |s: Src| matches!(s, Src::Val(k) if matches!(df.cells[k], LoopCell::Select { .. }));
430 df.cells.iter().any(|c| match *c {
431 LoopCell::Arith { a, b, .. } => is_select(a) || is_select(b),
432 LoopCell::Select { ca, cb, .. } => is_select(ca) || is_select(cb),
433 _ => false,
434 })
435}
436
437#[cfg(test)]
438#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/select_hoist/tests.rs"]
439mod tests;