use std::hash::Hash;
use celox_design::{BinaryOp, BitAccess, UnaryOp};
use celox_slt::{NodeId, SLTNode, SLTNodeArena, SLTNodeFactsError, get_width};
use num_bigint::BigUint;
pub fn lower_runtime_pow<A: Hash + Eq + Clone>(
arena: &mut SLTNodeArena<A>,
base: NodeId,
exponent: NodeId,
width: usize,
exponent_signed: bool,
result_signed: bool,
) -> Result<NodeId, SLTNodeFactsError> {
let exponent_width = get_width(exponent, arena);
debug_assert!(width > 0);
debug_assert!(exponent_width > 0);
let constant = |arena: &mut SLTNodeArena<A>, value: BigUint, mask: BigUint| {
arena.alloc(SLTNode::Constant(value, mask, width, result_signed))
};
let zero = constant(arena, BigUint::from(0u8), BigUint::from(0u8))?;
let one = constant(arena, BigUint::from(1u8), BigUint::from(0u8))?;
let width_mask = (BigUint::from(1u8) << width) - BigUint::from(1u8);
let minus_one = constant(arena, width_mask.clone(), BigUint::from(0u8))?;
let unknown = constant(arena, BigUint::from(0u8), width_mask)?;
let mut result = one;
let mut factor = base;
let exponent_lsb = arena.alloc(SLTNode::Slice {
expr: exponent,
access: BitAccess::new(0, 0),
})?;
let magnitude_width = exponent_width - usize::from(exponent_signed);
for bit_index in 0..magnitude_width {
let bit = arena.alloc(SLTNode::Slice {
expr: exponent,
access: BitAccess::new(bit_index, bit_index),
})?;
let product = arena.alloc(SLTNode::Binary(result, BinaryOp::Mul, factor))?;
result = arena.alloc(SLTNode::Mux {
cond: bit,
then_expr: product,
else_expr: result,
})?;
if bit_index + 1 != magnitude_width {
factor = arena.alloc(SLTNode::Binary(factor, BinaryOp::Mul, factor))?;
}
}
if exponent_signed {
let sign = arena.alloc(SLTNode::Slice {
expr: exponent,
access: BitAccess::new(exponent_width - 1, exponent_width - 1),
})?;
let base_is_zero = arena.alloc(SLTNode::Binary(base, BinaryOp::Eq, zero))?;
let base_is_one = arena.alloc(SLTNode::Binary(base, BinaryOp::Eq, one))?;
let base_is_minus_one = arena.alloc(SLTNode::Binary(base, BinaryOp::Eq, minus_one))?;
let minus_one_result = arena.alloc(SLTNode::Mux {
cond: exponent_lsb,
then_expr: minus_one,
else_expr: one,
})?;
let negative_nonzero = arena.alloc(SLTNode::Mux {
cond: base_is_minus_one,
then_expr: minus_one_result,
else_expr: zero,
})?;
let negative_nonzero = arena.alloc(SLTNode::Mux {
cond: base_is_one,
then_expr: one,
else_expr: negative_nonzero,
})?;
let negative_result = arena.alloc(SLTNode::Mux {
cond: base_is_zero,
then_expr: unknown,
else_expr: negative_nonzero,
})?;
result = arena.alloc(SLTNode::Mux {
cond: sign,
then_expr: negative_result,
else_expr: result,
})?;
}
let base_two_state = arena.alloc(SLTNode::Unary(UnaryOp::ToTwoState, base))?;
let exponent_two_state = arena.alloc(SLTNode::Unary(UnaryOp::ToTwoState, exponent))?;
let base_known = arena.alloc(SLTNode::Binary(base, BinaryOp::Eq, base_two_state))?;
let exponent_known =
arena.alloc(SLTNode::Binary(exponent, BinaryOp::Eq, exponent_two_state))?;
let operands_known = arena.alloc(SLTNode::Binary(
base_known,
BinaryOp::LogicAnd,
exponent_known,
))?;
arena.alloc(SLTNode::Mux {
cond: operands_known,
then_expr: result,
else_expr: unknown,
})
}