1use super::sva_to_verify::{BitVecBoundedOp, BoundedExpr, CmpBoundedOp};
14use logicaffeine_proof::ProofExpr;
15
16#[derive(Clone)]
18enum Bit {
19 Const(bool),
20 Dyn(ProofExpr),
21}
22
23fn b_not(x: Bit) -> Bit {
24 match x {
25 Bit::Const(c) => Bit::Const(!c),
26 Bit::Dyn(e) => Bit::Dyn(ProofExpr::Not(Box::new(e))),
27 }
28}
29
30fn b_and(a: Bit, b: Bit) -> Bit {
31 match (a, b) {
32 (Bit::Const(false), _) | (_, Bit::Const(false)) => Bit::Const(false),
33 (Bit::Const(true), y) | (y, Bit::Const(true)) => y,
34 (Bit::Dyn(x), Bit::Dyn(y)) => Bit::Dyn(ProofExpr::And(Box::new(x), Box::new(y))),
35 }
36}
37
38fn b_or(a: Bit, b: Bit) -> Bit {
39 match (a, b) {
40 (Bit::Const(true), _) | (_, Bit::Const(true)) => Bit::Const(true),
41 (Bit::Const(false), y) | (y, Bit::Const(false)) => y,
42 (Bit::Dyn(x), Bit::Dyn(y)) => Bit::Dyn(ProofExpr::Or(Box::new(x), Box::new(y))),
43 }
44}
45
46fn b_iff(a: Bit, b: Bit) -> Bit {
48 match (a, b) {
49 (Bit::Const(c), Bit::Const(d)) => Bit::Const(c == d),
50 (Bit::Const(true), y) | (y, Bit::Const(true)) => y,
51 (Bit::Const(false), y) | (y, Bit::Const(false)) => b_not(y),
52 (Bit::Dyn(x), Bit::Dyn(y)) => Bit::Dyn(ProofExpr::Iff(Box::new(x), Box::new(y))),
53 }
54}
55
56fn b_xor(a: Bit, b: Bit) -> Bit {
57 b_not(b_iff(a, b))
58}
59
60fn b_ite(s: Bit, t: Bit, e: Bit) -> Bit {
62 match s {
63 Bit::Const(true) => t,
64 Bit::Const(false) => e,
65 s => b_or(b_and(s.clone(), t), b_and(b_not(s), e)),
66 }
67}
68
69fn bit_to_proof(b: Bit) -> ProofExpr {
70 match b {
71 Bit::Const(true) => {
74 let c = ProofExpr::Atom("__bit_const".to_string());
75 ProofExpr::Or(Box::new(c.clone()), Box::new(ProofExpr::Not(Box::new(c))))
76 }
77 Bit::Const(false) => {
78 let c = ProofExpr::Atom("__bit_const".to_string());
79 ProofExpr::And(Box::new(c.clone()), Box::new(ProofExpr::Not(Box::new(c))))
80 }
81 Bit::Dyn(e) => e,
82 }
83}
84
85fn ripple_add(a: &[Bit], b: &[Bit], carry_in: Bit) -> (Vec<Bit>, Bit) {
89 let mut carry = carry_in;
90 let mut out = Vec::with_capacity(a.len());
91 for i in 0..a.len() {
92 let axb = b_xor(a[i].clone(), b[i].clone());
93 let sum = b_xor(axb.clone(), carry.clone());
94 let carry_next = b_or(
95 b_and(a[i].clone(), b[i].clone()),
96 b_and(carry.clone(), axb),
97 );
98 out.push(sum);
99 carry = carry_next;
100 }
101 (out, carry)
102}
103
104fn bv_add(a: &[Bit], b: &[Bit]) -> Vec<Bit> {
105 ripple_add(a, b, Bit::Const(false)).0
106}
107
108fn bv_sub(a: &[Bit], b: &[Bit]) -> Vec<Bit> {
110 let nb: Vec<Bit> = b.iter().cloned().map(b_not).collect();
111 ripple_add(a, &nb, Bit::Const(true)).0
112}
113
114fn bv_mul(a: &[Bit], b: &[Bit]) -> Vec<Bit> {
116 let w = a.len();
117 let mut acc: Vec<Bit> = vec![Bit::Const(false); w];
118 for i in 0..w {
119 let mut pp: Vec<Bit> = vec![Bit::Const(false); w];
121 for j in i..w {
122 pp[j] = b_and(a[j - i].clone(), b[i].clone());
123 }
124 acc = bv_add(&acc, &pp);
125 }
126 acc
127}
128
129fn shl_const(a: &[Bit], k: usize) -> Vec<Bit> {
131 let w = a.len();
132 (0..w)
133 .map(|j| if j >= k { a[j - k].clone() } else { Bit::Const(false) })
134 .collect()
135}
136
137fn shr_const(a: &[Bit], k: usize, fill: Bit) -> Vec<Bit> {
139 let w = a.len();
140 (0..w)
141 .map(|j| if j + k < w { a[j + k].clone() } else { fill.clone() })
142 .collect()
143}
144
145fn barrel_shift(a: &[Bit], amount: &[Bit], left: bool, fill: Bit) -> Vec<Bit> {
149 let w = a.len();
150 let mut cur = a.to_vec();
151 for (s, amt_bit) in amount.iter().enumerate() {
152 let dist = 1usize << s;
153 if dist >= w {
154 let shifted = vec![fill.clone(); w];
156 cur = (0..w)
157 .map(|j| b_ite(amt_bit.clone(), shifted[j].clone(), cur[j].clone()))
158 .collect();
159 continue;
160 }
161 let shifted = if left {
162 shl_const(&cur, dist)
163 } else {
164 shr_const(&cur, dist, fill.clone())
165 };
166 cur = (0..w)
167 .map(|j| b_ite(amt_bit.clone(), shifted[j].clone(), cur[j].clone()))
168 .collect();
169 }
170 cur
171}
172
173fn bv_eq(a: &[Bit], b: &[Bit]) -> Bit {
175 let mut acc = Bit::Const(true);
176 for i in 0..a.len() {
177 acc = b_and(acc, b_iff(a[i].clone(), b[i].clone()));
178 }
179 acc
180}
181
182fn bv_ult(a: &[Bit], b: &[Bit]) -> Bit {
184 let mut lt = Bit::Const(false);
185 for i in 0..a.len() {
186 let bit_lt = b_and(b_not(a[i].clone()), b[i].clone());
188 let bit_eq = b_iff(a[i].clone(), b[i].clone());
189 lt = b_or(bit_lt, b_and(bit_eq, lt));
190 }
191 lt
192}
193
194fn bv_slt(a: &[Bit], b: &[Bit]) -> Bit {
196 let w = a.len();
197 let sa = a[w - 1].clone();
198 let sb = b[w - 1].clone();
199 b_ite(b_xor(sa.clone(), sb), sa, bv_ult(a, b))
202}
203
204fn blast_bits(e: &BoundedExpr) -> Option<Vec<Bit>> {
209 match e {
210 BoundedExpr::BitVecConst { width, value } => {
211 Some((0..*width).map(|i| Bit::Const((value >> i) & 1 == 1)).collect())
212 }
213 BoundedExpr::BitVecVar(name, width) => Some(
214 (0..*width)
215 .map(|i| Bit::Dyn(ProofExpr::Atom(format!("{name}#{i}"))))
216 .collect(),
217 ),
218 BoundedExpr::BitVecExtract { high, low, operand } => {
219 let bits = blast_bits(operand)?;
220 let (lo, hi) = (*low as usize, *high as usize);
221 if hi >= bits.len() || lo > hi {
222 return None;
223 }
224 Some(bits[lo..=hi].to_vec())
225 }
226 BoundedExpr::BitVecConcat(a, b) => {
227 let mut bits = blast_bits(b)?;
229 bits.extend(blast_bits(a)?);
230 Some(bits)
231 }
232 BoundedExpr::BitVecBinary { op, left, right } => {
233 let a = blast_bits(left)?;
234 let b = blast_bits(right)?;
235 match op {
236 BitVecBoundedOp::Not => Some(a.into_iter().map(b_not).collect()),
237 _ if a.len() != b.len() => None,
239 BitVecBoundedOp::And => Some(zip_map(a, b, b_and)),
240 BitVecBoundedOp::Or => Some(zip_map(a, b, b_or)),
241 BitVecBoundedOp::Xor => Some(zip_map(a, b, b_xor)),
242 BitVecBoundedOp::Add => Some(bv_add(&a, &b)),
243 BitVecBoundedOp::Sub => Some(bv_sub(&a, &b)),
244 BitVecBoundedOp::Mul => Some(bv_mul(&a, &b)),
245 BitVecBoundedOp::Shl => Some(barrel_shift(&a, &b, true, Bit::Const(false))),
246 BitVecBoundedOp::Shr => Some(barrel_shift(&a, &b, false, Bit::Const(false))),
247 BitVecBoundedOp::AShr => {
248 let sign = a[a.len() - 1].clone();
249 Some(barrel_shift(&a, &b, false, sign))
250 }
251 BitVecBoundedOp::Eq | BitVecBoundedOp::ULt | BitVecBoundedOp::SLt => None,
253 }
254 }
255 _ => None,
256 }
257}
258
259fn zip_map(a: Vec<Bit>, b: Vec<Bit>, f: fn(Bit, Bit) -> Bit) -> Vec<Bit> {
260 a.into_iter().zip(b).map(|(x, y)| f(x, y)).collect()
261}
262
263pub fn lower_bool(e: &BoundedExpr) -> Option<ProofExpr> {
266 let bit = match e {
267 BoundedExpr::BitVecBinary { op, left, right } => {
268 let a = blast_bits(left)?;
269 let b = blast_bits(right)?;
270 if a.len() != b.len() {
271 return None;
272 }
273 match op {
274 BitVecBoundedOp::Eq => bv_eq(&a, &b),
275 BitVecBoundedOp::ULt => bv_ult(&a, &b),
276 BitVecBoundedOp::SLt => bv_slt(&a, &b),
277 _ => return None,
278 }
279 }
280 BoundedExpr::Eq(l, r) => {
281 let a = blast_bits(l)?;
282 let b = blast_bits(r)?;
283 if a.len() != b.len() {
284 return None;
285 }
286 bv_eq(&a, &b)
287 }
288 BoundedExpr::Lt(l, r) | BoundedExpr::Gt(l, r) | BoundedExpr::Lte(l, r)
289 | BoundedExpr::Gte(l, r) => bv_compare(e, l, r)?,
290 BoundedExpr::Comparison { op, left, right } => {
291 let a = blast_bits(left)?;
292 let b = blast_bits(right)?;
293 if a.len() != b.len() {
294 return None;
295 }
296 match op {
297 CmpBoundedOp::Lt => bv_ult(&a, &b),
298 CmpBoundedOp::Gt => bv_ult(&b, &a),
299 CmpBoundedOp::Lte => b_not(bv_ult(&b, &a)),
300 CmpBoundedOp::Gte => b_not(bv_ult(&a, &b)),
301 }
302 }
303 _ => return None,
304 };
305 Some(bit_to_proof(bit))
306}
307
308fn bv_compare(e: &BoundedExpr, l: &BoundedExpr, r: &BoundedExpr) -> Option<Bit> {
310 let a = blast_bits(l)?;
311 let b = blast_bits(r)?;
312 if a.len() != b.len() {
313 return None;
314 }
315 Some(match e {
316 BoundedExpr::Lt(..) => bv_ult(&a, &b),
317 BoundedExpr::Gt(..) => bv_ult(&b, &a),
318 BoundedExpr::Lte(..) => b_not(bv_ult(&b, &a)),
319 BoundedExpr::Gte(..) => b_not(bv_ult(&a, &b)),
320 _ => return None,
321 })
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use logicaffeine_proof::sat::{prove_unsat, UnsatOutcome};
328
329 const W: u32 = 4;
330 const MASK: u64 = (1 << W) - 1;
331
332 fn konst(v: u64) -> BoundedExpr {
333 BoundedExpr::BitVecConst { width: W, value: v & MASK }
334 }
335 fn bv_bin(op: BitVecBoundedOp, a: u64, b: u64) -> BoundedExpr {
336 BoundedExpr::BitVecBinary { op, left: Box::new(konst(a)), right: Box::new(konst(b)) }
337 }
338 fn assert_bool(e: &BoundedExpr, expect_true: bool) {
341 let p = lower_bool(e).expect("lowered to a boolean");
342 if expect_true {
343 assert_eq!(prove_unsat(&ProofExpr::Not(Box::new(p))), UnsatOutcome::Refuted);
344 } else {
345 assert_eq!(prove_unsat(&p), UnsatOutcome::Refuted);
346 }
347 }
348 fn assert_bv(op: BitVecBoundedOp, a: u64, b: u64, expected: u64) {
351 let eq = BoundedExpr::Eq(Box::new(bv_bin(op.clone(), a, b)), Box::new(konst(expected)));
352 assert_bool(&eq, true);
353 let neq =
354 BoundedExpr::Eq(Box::new(bv_bin(op, a, b)), Box::new(konst(expected.wrapping_add(1))));
355 assert_bool(&neq, false);
356 }
357
358 #[test]
359 fn add_sub_mul_match_integer_oracle_exhaustively() {
360 for a in 0..=MASK {
361 for b in 0..=MASK {
362 assert_bv(BitVecBoundedOp::Add, a, b, (a + b) & MASK);
363 assert_bv(BitVecBoundedOp::Sub, a, b, a.wrapping_sub(b) & MASK);
364 assert_bv(BitVecBoundedOp::Mul, a, b, (a * b) & MASK);
365 }
366 }
367 }
368
369 #[test]
370 fn bitwise_ops_match_oracle_exhaustively() {
371 for a in 0..=MASK {
372 for b in 0..=MASK {
373 assert_bv(BitVecBoundedOp::And, a, b, a & b);
374 assert_bv(BitVecBoundedOp::Or, a, b, a | b);
375 assert_bv(BitVecBoundedOp::Xor, a, b, a ^ b);
376 }
377 }
378 for a in 0..=MASK {
380 let e = BoundedExpr::Eq(
381 Box::new(BoundedExpr::BitVecBinary {
382 op: BitVecBoundedOp::Not,
383 left: Box::new(konst(a)),
384 right: Box::new(konst(0)),
385 }),
386 Box::new(konst(!a & MASK)),
387 );
388 assert_bool(&e, true);
389 }
390 }
391
392 #[test]
393 fn shifts_match_oracle_exhaustively() {
394 for a in 0..=MASK {
395 for s in 0..=MASK {
396 let sh = (s & MASK) as u32;
397 let shl = if sh < W { (a << sh) & MASK } else { 0 };
398 assert_bv(BitVecBoundedOp::Shl, a, s, shl);
399 let shr = if sh < W { (a & MASK) >> sh } else { 0 };
400 assert_bv(BitVecBoundedOp::Shr, a, s, shr);
401 let sign = (a >> (W - 1)) & 1 == 1;
403 let ashr = if sh >= W {
404 if sign { MASK } else { 0 }
405 } else {
406 let base = (a & MASK) >> sh;
407 if sign {
408 base | (MASK & !((1 << (W - sh)) - 1))
409 } else {
410 base
411 }
412 };
413 assert_bv(BitVecBoundedOp::AShr, a, s, ashr & MASK);
414 }
415 }
416 }
417
418 #[test]
419 fn comparisons_match_oracle_exhaustively() {
420 for a in 0..=MASK {
421 for b in 0..=MASK {
422 assert_bool(&bv_bin(BitVecBoundedOp::Eq, a, b), a == b);
423 assert_bool(&bv_bin(BitVecBoundedOp::ULt, a, b), a < b);
424 let sa = sign_extend(a);
426 let sb = sign_extend(b);
427 assert_bool(&bv_bin(BitVecBoundedOp::SLt, a, b), sa < sb);
428 assert_bool(&BoundedExpr::Lt(Box::new(konst(a)), Box::new(konst(b))), a < b);
430 assert_bool(&BoundedExpr::Gte(Box::new(konst(a)), Box::new(konst(b))), a >= b);
431 }
432 }
433 }
434
435 fn sign_extend(v: u64) -> i64 {
436 let v = (v & MASK) as i64;
437 if (v >> (W - 1)) & 1 == 1 {
438 v - (1 << W)
439 } else {
440 v
441 }
442 }
443
444 #[test]
445 fn extract_and_concat_match_oracle() {
446 for a in 0..=MASK {
448 let ex = BoundedExpr::BitVecExtract {
449 high: 2,
450 low: 1,
451 operand: Box::new(konst(a)),
452 };
453 let expected = (a >> 1) & 0b11;
454 let e = BoundedExpr::Eq(
455 Box::new(ex),
456 Box::new(BoundedExpr::BitVecConst { width: 2, value: expected }),
457 );
458 assert_bool(&e, true);
459 }
460 for a in 0..4 {
462 for b in 0..4 {
463 let cc = BoundedExpr::BitVecConcat(
464 Box::new(BoundedExpr::BitVecConst { width: 2, value: a }),
465 Box::new(BoundedExpr::BitVecConst { width: 2, value: b }),
466 );
467 let e = BoundedExpr::Eq(Box::new(cc), Box::new(konst((a << 2) | b)));
468 assert_bool(&e, true);
469 }
470 }
471 }
472
473 #[test]
474 fn datapath_equivalence_over_variables() {
475 let x = || Box::new(BoundedExpr::BitVecVar("x".to_string(), W));
477 let y = || Box::new(BoundedExpr::BitVecVar("y".to_string(), W));
478 let xy = BoundedExpr::BitVecBinary { op: BitVecBoundedOp::Add, left: x(), right: y() };
479 let yx = BoundedExpr::BitVecBinary { op: BitVecBoundedOp::Add, left: y(), right: x() };
480 let eq = BoundedExpr::Eq(Box::new(xy), Box::new(yx));
481 let p = lower_bool(&eq).unwrap();
482 assert_eq!(prove_unsat(&ProofExpr::Not(Box::new(p))), UnsatOutcome::Refuted);
483 }
484
485 #[test]
486 fn rotl_is_a_bit_permutation_at_full_32_bits() {
487 const WW: u32 = 32;
493 fn rotl(name: &str, k: u32) -> BoundedExpr {
495 let v = || Box::new(BoundedExpr::BitVecVar(name.to_string(), WW));
496 let kc = |s: u32| Box::new(BoundedExpr::BitVecConst { width: WW, value: s as u64 });
497 let hi = Box::new(BoundedExpr::BitVecBinary { op: BitVecBoundedOp::Shl, left: v(), right: kc(k) });
498 let lo =
499 Box::new(BoundedExpr::BitVecBinary { op: BitVecBoundedOp::Shr, left: v(), right: kc(WW - k) });
500 BoundedExpr::BitVecBinary { op: BitVecBoundedOp::Or, left: hi, right: lo }
501 }
502 let var = |n: &str| Box::new(BoundedExpr::BitVecVar(n.to_string(), WW));
503
504 for k in [7u32, 8, 12, 16] {
506 let eq_rot = lower_bool(&BoundedExpr::Eq(Box::new(rotl("x", k)), Box::new(rotl("y", k))))
509 .expect("rotation equality lowered");
510 let eq_in = lower_bool(&BoundedExpr::Eq(var("x"), var("y"))).expect("input equality lowered");
511 let counter = ProofExpr::And(Box::new(eq_rot), Box::new(ProofExpr::Not(Box::new(eq_in))));
512 assert_eq!(
513 prove_unsat(&counter),
514 UnsatOutcome::Refuted,
515 "rotl by {k} must be injective (a bit-permutation) over all 32-bit inputs"
516 );
517 }
518
519 let zero_and = |n: &str| BoundedExpr::BitVecBinary {
523 op: BitVecBoundedOp::And,
524 left: Box::new(BoundedExpr::BitVecVar(n.to_string(), WW)),
525 right: Box::new(BoundedExpr::BitVecConst { width: WW, value: 0 }),
526 };
527 let eq_img = lower_bool(&BoundedExpr::Eq(Box::new(zero_and("x")), Box::new(zero_and("y"))))
528 .expect("image equality lowered");
529 let eq_in = lower_bool(&BoundedExpr::Eq(var("x"), var("y"))).expect("input equality lowered");
530 let counter = ProofExpr::And(Box::new(eq_img), Box::new(ProofExpr::Not(Box::new(eq_in))));
531 assert_ne!(
532 prove_unsat(&counter),
533 UnsatOutcome::Refuted,
534 "x & 0 is NOT injective — the prover must not vacuously refute its counterexample"
535 );
536 }
537}