1use std::fmt::{Debug, Display};
10use std::hash::Hash;
11
12use num_bigint::BigUint;
13use num_traits::Zero;
14
15use celox_design::{BinaryOp, BitAccess, UnaryOp};
16
17use crate::{
18 HashMap, HashSet, LogicPath, NodeId, SLTForFoldGroupState, SLTNode, SLTNodeArena,
19 SLTNodeFactsError, get_width,
20};
21
22fn is_const_expr<A: Clone + Eq + Hash>(node: NodeId, arena: &SLTNodeArena<A>) -> bool {
26 match arena.get(node) {
27 SLTNode::Constant(..) => true,
28 SLTNode::Slice { expr, .. } => is_const_expr(*expr, arena),
29 SLTNode::Binary(l, _, r) => is_const_expr(*l, arena) && is_const_expr(*r, arena),
30 SLTNode::Unary(_, inner) => is_const_expr(*inner, arena),
31 SLTNode::Concat(parts) => parts.iter().all(|(id, _)| is_const_expr(*id, arena)),
32 SLTNode::ForFoldGroup { .. } => false,
35 _ => false,
36 }
37}
38
39fn eval_const_expr<A: Clone + Eq + Hash + Debug>(
41 node: NodeId,
42 arena: &SLTNodeArena<A>,
43) -> (BigUint, BigUint, usize) {
44 let width = get_width(node, arena);
45 let width_mask = if width > 0 {
46 (BigUint::from(1u32) << width) - 1u32
47 } else {
48 BigUint::from(0u32)
49 };
50
51 match arena.get(node) {
52 SLTNode::Constant(val, msk, _, _) => (val.clone(), msk.clone(), width),
53 SLTNode::Slice { expr, access } => {
54 let (val, msk, _) = eval_const_expr(*expr, arena);
55 let slice_w = access.msb - access.lsb + 1;
56 let slice_mask = (BigUint::from(1u32) << slice_w) - 1u32;
57 (
58 (&val >> access.lsb) & &slice_mask,
59 (&msk >> access.lsb) & &slice_mask,
60 slice_w,
61 )
62 }
63 SLTNode::Binary(l, op, r) => {
64 let (lv, lm, _) = eval_const_expr(*l, arena);
65 let (rv, rm, _) = eval_const_expr(*r, arena);
66 if lm != BigUint::from(0u32) || rm != BigUint::from(0u32) {
68 return (BigUint::from(0u32), width_mask.clone(), width); }
70 let result = match op {
71 BinaryOp::And => &lv & &rv,
72 BinaryOp::Or => &lv | &rv,
73 BinaryOp::Xor => &lv ^ &rv,
74 BinaryOp::Add => (&lv + &rv) & &width_mask,
75 BinaryOp::Sub => {
76 let total = (&width_mask + 1u32) + &lv - &rv;
78 total & &width_mask
79 }
80 BinaryOp::Shl => {
81 if let Some(shift) = rv.to_u64_digits().first().copied() {
82 (&lv << shift as usize) & &width_mask
83 } else {
84 BigUint::from(0u32)
85 }
86 }
87 BinaryOp::Shr => {
88 if let Some(shift) = rv.to_u64_digits().first().copied() {
89 &lv >> shift as usize
90 } else {
91 BigUint::from(0u32)
92 }
93 }
94 _ => return (BigUint::from(0u32), width_mask.clone(), width),
95 };
96 (result & &width_mask, BigUint::from(0u32), width)
97 }
98 SLTNode::Unary(op, inner) => {
99 let (v, m, inner_width) = eval_const_expr(*inner, arena);
100 if matches!(op, UnaryOp::ToTwoState) {
101 return (
102 v & (&width_mask ^ (&m & &width_mask)),
103 BigUint::from(0u32),
104 width,
105 );
106 }
107 if matches!(op, UnaryOp::LogicNot | UnaryOp::Or) {
108 let inner_width_mask = if inner_width > 0 {
109 (BigUint::from(1u32) << inner_width) - 1u32
110 } else {
111 BigUint::from(0u32)
112 };
113 let unknown = &m & &inner_width_mask;
114 let known = &inner_width_mask ^ &unknown;
115 let definite_ones = (&v & &inner_width_mask) & known;
116 let has_unknown = !unknown.is_zero();
117 let (value, mask) = match op {
118 UnaryOp::LogicNot => {
119 if !definite_ones.is_zero() {
120 (0u8, 0u8)
121 } else if has_unknown {
122 (1u8, 1u8)
123 } else {
124 (1u8, 0u8)
125 }
126 }
127 UnaryOp::Or => {
128 if !definite_ones.is_zero() {
129 (1u8, 0u8)
130 } else if has_unknown {
131 (1u8, 1u8)
132 } else {
133 (0u8, 0u8)
134 }
135 }
136 _ => unreachable!(),
137 };
138 return (BigUint::from(value), BigUint::from(mask), width);
139 }
140 if m != BigUint::from(0u32) {
141 return (BigUint::from(0u32), width_mask.clone(), width);
142 }
143 let result = match op {
144 UnaryOp::BitNot => (&width_mask) ^ &v,
145 UnaryOp::PopCount => BigUint::from(
146 v.iter_u64_digits()
147 .map(|digit| digit.count_ones() as usize)
148 .sum::<usize>(),
149 ),
150 UnaryOp::CountLeadingZeros => {
151 BigUint::from(inner_width.saturating_sub(v.bits() as usize))
152 }
153 UnaryOp::CountTrailingZeros => {
154 let zeros = v
155 .iter_u64_digits()
156 .enumerate()
157 .find_map(|(index, digit)| {
158 (digit != 0).then_some(
159 index * u64::BITS as usize + digit.trailing_zeros() as usize,
160 )
161 })
162 .unwrap_or(inner_width)
163 .min(inner_width);
164 BigUint::from(zeros)
165 }
166 _ => return (BigUint::from(0u32), width_mask.clone(), width),
167 };
168 (result & &width_mask, BigUint::from(0u32), width)
169 }
170 SLTNode::ForFoldGroup { .. } => (BigUint::from(0u32), width_mask, width),
171 _ => (BigUint::from(0u32), width_mask, width),
172 }
173}
174
175struct ConstVar {
177 payload: BigUint,
179 mask: BigUint,
181}
182
183pub fn inline_constant_variables<A: Clone + Eq + Hash + Debug + Display>(
187 paths: &mut [LogicPath<A>],
188 arena: &mut SLTNodeArena<A>,
189) -> Result<bool, SLTNodeFactsError> {
190 let mut const_candidates: HashMap<A, Vec<(BitAccess, NodeId)>> = HashMap::default();
194 let mut non_const: HashSet<A> = HashSet::default();
195
196 for path in paths.iter() {
197 let Some(target) = path.target.var() else {
198 continue;
199 };
200 let var = &target.id;
201 if non_const.contains(var) {
202 continue;
203 }
204 if is_const_expr(path.expr, arena) {
205 const_candidates
206 .entry(var.clone())
207 .or_default()
208 .push((target.access, path.expr));
209 } else {
210 non_const.insert(var.clone());
211 const_candidates.remove(var);
212 }
213 }
214
215 for node in arena.iter() {
218 if let SLTNode::Input {
219 variable, index, ..
220 } = node
221 && !index.is_empty()
222 {
223 non_const.insert(variable.clone());
224 const_candidates.remove(variable);
225 }
226 }
227
228 if const_candidates.is_empty() {
229 return Ok(false);
230 }
231
232 let mut const_vars: HashMap<A, ConstVar> = HashMap::default();
234 for (var, entries) in &const_candidates {
235 let total_width: usize = entries
237 .iter()
238 .map(|(access, _)| access.msb + 1)
239 .max()
240 .unwrap_or(0);
241 if total_width == 0 {
242 continue;
243 }
244
245 let mut payload = BigUint::from(0u32);
246 let mut mask = BigUint::from(0u32);
247
248 for &(access, expr) in entries {
249 let (val, msk, _) = eval_const_expr(expr, arena);
250 let entry_width = access.msb - access.lsb + 1;
251 let entry_mask_bits: BigUint = (BigUint::from(1u32) << entry_width) - 1u32;
252 payload |= (&val & &entry_mask_bits) << access.lsb;
253 mask |= (&msk & &entry_mask_bits) << access.lsb;
254 }
255
256 if mask != BigUint::from(0u32) {
258 continue;
259 }
260 const_vars.insert(var.clone(), ConstVar { payload, mask });
261 }
262
263 if const_vars.is_empty() {
264 return Ok(false);
265 }
266
267 let mut rewrite_cache: HashMap<NodeId, NodeId> = HashMap::default();
272 for path in paths.iter_mut() {
273 if path.sources.iter().any(|s| const_vars.contains_key(&s.id)) {
274 path.expr = rewrite_expr(path.expr, arena, &const_vars, &mut rewrite_cache)?;
275 path.sources.retain(|src| !const_vars.contains_key(&src.id));
276 path.previous_sources
277 .retain(|src| !const_vars.contains_key(&src.id));
278 path.address_sources
279 .retain(|src| !const_vars.contains_key(&src.id));
280 }
281 }
282
283 Ok(true)
288}
289
290fn rewrite_expr<A: Clone + Eq + Hash + Debug + Display>(
293 node: NodeId,
294 arena: &mut SLTNodeArena<A>,
295 const_vars: &HashMap<A, ConstVar>,
296 cache: &mut HashMap<NodeId, NodeId>,
297) -> Result<NodeId, SLTNodeFactsError> {
298 if let Some(&cached) = cache.get(&node) {
299 return Ok(cached);
300 }
301
302 let result = match arena.get(node).clone() {
303 SLTNode::Input {
304 variable,
305 index,
306 access,
307 ..
308 } if index.is_empty() => {
309 if let Some(cv) = const_vars.get(&variable) {
310 let width = access.msb - access.lsb + 1;
311 let bit_mask = (BigUint::from(1u32) << width) - 1u32;
312 let val = (&cv.payload >> access.lsb) & &bit_mask;
313 let msk = (&cv.mask >> access.lsb) & &bit_mask;
314 arena.alloc(SLTNode::Constant(val, msk, width, false))?
315 } else {
316 node
317 }
318 }
319 SLTNode::Slice { expr, access } => {
320 let new_expr = rewrite_expr(expr, arena, const_vars, cache)?;
321 if new_expr == expr {
322 node
323 } else {
324 arena.alloc(SLTNode::Slice {
325 expr: new_expr,
326 access,
327 })?
328 }
329 }
330 SLTNode::Binary(l, op, r) => {
331 let new_l = rewrite_expr(l, arena, const_vars, cache)?;
332 let new_r = rewrite_expr(r, arena, const_vars, cache)?;
333 if new_l == l && new_r == r {
334 node
335 } else {
336 arena.alloc(SLTNode::Binary(new_l, op, new_r))?
337 }
338 }
339 SLTNode::Unary(op, inner) => {
340 let new_inner = rewrite_expr(inner, arena, const_vars, cache)?;
341 if new_inner == inner {
342 node
343 } else {
344 arena.alloc(SLTNode::Unary(op, new_inner))?
345 }
346 }
347 SLTNode::Concat(parts) => {
348 let new_parts: Vec<_> = parts
349 .iter()
350 .map(|&(id, w)| Ok((rewrite_expr(id, arena, const_vars, cache)?, w)))
351 .collect::<Result<_, SLTNodeFactsError>>()?;
352 if new_parts.iter().zip(parts.iter()).all(|(a, b)| a.0 == b.0) {
353 node
354 } else {
355 arena.alloc(SLTNode::Concat(new_parts))?
356 }
357 }
358 SLTNode::Mux {
359 cond,
360 then_expr,
361 else_expr,
362 } => {
363 let new_cond = rewrite_expr(cond, arena, const_vars, cache)?;
364 let new_then = rewrite_expr(then_expr, arena, const_vars, cache)?;
365 let new_else = rewrite_expr(else_expr, arena, const_vars, cache)?;
366 if new_cond == cond && new_then == then_expr && new_else == else_expr {
367 node
368 } else {
369 arena.alloc(SLTNode::Mux {
370 cond: new_cond,
371 then_expr: new_then,
372 else_expr: new_else,
373 })?
374 }
375 }
376 SLTNode::ForFoldGroup {
377 loop_var,
378 loop_width,
379 loop_signed,
380 start,
381 step,
382 trip_count,
383 entry_guard,
384 states,
385 } => {
386 let binding_is_constant = const_vars.contains_key(&loop_var)
391 || states
392 .iter()
393 .any(|state| const_vars.contains_key(&state.target.id));
394 if binding_is_constant {
395 node
396 } else {
397 let new_entry_guard = rewrite_expr(entry_guard, arena, const_vars, cache)?;
398 let new_states = states
399 .iter()
400 .map(|state| {
401 Ok(SLTForFoldGroupState {
402 target: state.target.clone(),
403 initial: rewrite_expr(state.initial, arena, const_vars, cache)?,
404 update: rewrite_expr(state.update, arena, const_vars, cache)?,
405 })
406 })
407 .collect::<Result<Vec<_>, SLTNodeFactsError>>()?;
408 let unchanged = new_entry_guard == entry_guard
409 && new_states
410 .iter()
411 .zip(&states)
412 .all(|(new, old)| new.initial == old.initial && new.update == old.update);
413 if unchanged {
414 node
415 } else {
416 arena.alloc(SLTNode::ForFoldGroup {
417 loop_var,
418 loop_width,
419 loop_signed,
420 start,
421 step,
422 trip_count,
423 entry_guard: new_entry_guard,
424 states: new_states,
425 })?
426 }
427 }
428 }
429 _ => node,
430 };
431
432 cache.insert(node, result);
433 Ok(result)
434}
435
436#[cfg(test)]
437mod tests {
438 use crate::{SLTNode, SLTNodeArena};
439 use celox_design::UnaryOp;
440 use num_bigint::BigUint;
441
442 use super::eval_const_expr;
443
444 #[test]
445 fn evaluates_two_state_bit_count_constants() {
446 let mut arena = SLTNodeArena::<u32>::new();
447 let value = arena
448 .alloc(SLTNode::Constant(
449 BigUint::from(0b0011_0100u8),
450 BigUint::from(0u8),
451 8,
452 false,
453 ))
454 .unwrap();
455
456 for (op, expected) in [
457 (UnaryOp::PopCount, 3u8),
458 (UnaryOp::CountLeadingZeros, 2u8),
459 (UnaryOp::CountTrailingZeros, 2u8),
460 ] {
461 let node = arena.alloc(SLTNode::Unary(op, value)).unwrap();
462 assert_eq!(
463 eval_const_expr(node, &arena),
464 (BigUint::from(expected), BigUint::from(0u8), 4),
465 );
466 }
467 }
468
469 #[test]
470 fn zero_has_full_operand_width_leading_and_trailing_zero_counts() {
471 let mut arena = SLTNodeArena::<u32>::new();
472 let zero = arena
473 .alloc(SLTNode::Constant(
474 BigUint::from(0u8),
475 BigUint::from(0u8),
476 8,
477 false,
478 ))
479 .unwrap();
480
481 for op in [UnaryOp::CountLeadingZeros, UnaryOp::CountTrailingZeros] {
482 let node = arena.alloc(SLTNode::Unary(op, zero)).unwrap();
483 assert_eq!(
484 eval_const_expr(node, &arena),
485 (BigUint::from(8u8), BigUint::from(0u8), 4),
486 );
487 }
488 }
489
490 #[test]
491 fn logical_not_and_reduction_or_constants_use_dominant_known_one() {
492 let mut arena = SLTNodeArena::<u32>::new();
493 let known_one_and_x = arena
494 .alloc(SLTNode::Constant(
495 BigUint::from(0b1000_0100u8),
496 BigUint::from(0b0000_0100u8),
497 8,
498 false,
499 ))
500 .unwrap();
501 let only_x = arena
502 .alloc(SLTNode::Constant(
503 BigUint::from(0b0000_0100u8),
504 BigUint::from(0b0000_0100u8),
505 8,
506 false,
507 ))
508 .unwrap();
509 let only_z = arena
510 .alloc(SLTNode::Constant(
511 BigUint::from(0u8),
512 BigUint::from(0b0000_0100u8),
513 8,
514 false,
515 ))
516 .unwrap();
517
518 let known_not = arena
519 .alloc(SLTNode::Unary(UnaryOp::LogicNot, known_one_and_x))
520 .unwrap();
521 let known_or = arena
522 .alloc(SLTNode::Unary(UnaryOp::Or, known_one_and_x))
523 .unwrap();
524 assert_eq!(
525 eval_const_expr(known_not, &arena),
526 (BigUint::from(0u8), BigUint::from(0u8), 1),
527 );
528 assert_eq!(
529 eval_const_expr(known_or, &arena),
530 (BigUint::from(1u8), BigUint::from(0u8), 1),
531 );
532
533 for inner in [only_x, only_z] {
534 for op in [UnaryOp::LogicNot, UnaryOp::Or] {
535 let node = arena.alloc(SLTNode::Unary(op, inner)).unwrap();
536 assert_eq!(
537 eval_const_expr(node, &arena),
538 (BigUint::from(1u8), BigUint::from(1u8), 1),
539 "{op:?}",
540 );
541 }
542 }
543 }
544}