logicaffeine_compile/codegen_sva/reverse_module.rs
1//! R5 — the SVA REVERSE-MODULE OBLIGATION for Hardware Studio.
2//!
3//! A Silicon functional unit `U` is reversible iff there exists a module `R` with `R ∘ U = id`. This
4//! discharges that obligation for a combinational unit THREE independent ways that MUST AGREE:
5//!
6//! 1. **Exhaustive bit-blast** — the unit's `2ⁿ`-row truth table is a permutation (each output
7//! distinct); its inverse permutation IS the constructive reverse module `R`.
8//! 2. **Algebraic certificate** — for a linear (CNOT/XOR) unit, its GF(2) transfer matrix is
9//! invertible (`∈ GL(n,2)`); reused from [`logicaffeine_proof::revgate`].
10//! 3. **Z3 equivalence** — (behind the `verification` feature) `check_z3_hw_equivalence` proves the
11//! composed `R ∘ U` structurally equals the identity.
12//!
13//! The obligation FAILS LOUDLY if any two discharged methods disagree — the "our bit-blaster == Z3 ==
14//! algebra" pattern from `phase_hw_native_vs_z3.rs`, now for reversibility. It emits the SVA property
15//! that pins `R(U(x)) == x`, the hardware-level twin of the software `forward ; reverse ≡ id` lock.
16
17use logicaffeine_proof::revgate::{certify_gate, certify_linear, GateCert};
18
19/// A discharged reverse-module obligation: the constructive reverse gate plus the SVA property that
20/// pins the reverse-module identity, produced only when every attempted method AGREED that `U` is
21/// reversible.
22#[derive(Debug, Clone)]
23pub struct ReverseModuleObligation {
24 pub name: String,
25 pub bits: u32,
26 /// The reverse module `R` as a truth table — the constructive inverse with `R ∘ U = id`.
27 pub reverse: Vec<usize>,
28 /// The SVA property text asserting `R(U(x)) == x` over every input.
29 pub sva_property: String,
30}
31
32/// Discharge the reverse-module obligation for the `n`-bit combinational unit `truth_table`. Proves
33/// reversibility by exhaustive bit-blast AND — when `linear_rows` (the GF(2) transfer matrix) is
34/// given — the algebraic GF(2) certificate, and REQUIRES the two verdicts to agree. On success
35/// returns the reverse module + SVA property; on failure names the erasure or the method disagreement.
36pub fn discharge_reverse_module(
37 name: &str,
38 n: u32,
39 truth_table: &[usize],
40 linear_rows: Option<&[u64]>,
41) -> Result<ReverseModuleObligation, String> {
42 // Way 1 — exhaustive bit-blast: the truth table must be a permutation; its inverse is `R`.
43 let reverse = match certify_gate(n, truth_table) {
44 GateCert::Reversible { inverse } => inverse,
45 GateCert::Collision {
46 input_a,
47 input_b,
48 output,
49 } => {
50 return Err(format!(
51 "{name}: irreversible — inputs {input_a} and {input_b} both map to output {output} (erasure)"
52 ));
53 }
54 };
55 // Way 2 — algebraic GF(2) (linear units): reversible iff the transfer matrix ∈ GL(n,2). Requiring
56 // it to AGREE with the exhaustive verdict is what makes the obligation trustworthy.
57 if let Some(rows) = linear_rows {
58 if !certify_linear(n, rows) {
59 return Err(format!(
60 "{name}: methods DISAGREE — the exhaustive bit-blast proved `U` reversible, but its GF(2) matrix is singular"
61 ));
62 }
63 }
64 let sva_property = emit_reverse_module_sva(name, n, truth_table, &reverse);
65 Ok(ReverseModuleObligation {
66 name: name.to_string(),
67 bits: n,
68 reverse,
69 sva_property,
70 })
71}
72
73/// Emit the SVA property pinning `R(U(x)) == x` as an exhaustive case over the `2ⁿ` inputs — the
74/// reverse-module identity as a checkable hardware assertion.
75fn emit_reverse_module_sva(name: &str, n: u32, table: &[usize], reverse: &[usize]) -> String {
76 let mut s = String::new();
77 s.push_str(&format!(
78 "// Reverse-module obligation: R ∘ U = id for the {n}-bit unit `{name}`\n"
79 ));
80 s.push_str(&format!(
81 "property p_reverse_module_{name};\n @(posedge clk) case (x)\n"
82 ));
83 for x in 0..(1usize << n) {
84 // U(x) = table[x]; R(U(x)) = reverse[table[x]] — the obligation asserts it equals x.
85 let r_of_u = reverse[table[x]];
86 s.push_str(&format!(
87 " {n}'d{x}: (r == {n}'d{r_of_u}); // U({x})={}, R(·)={r_of_u}\n",
88 table[x]
89 ));
90 }
91 s.push_str(&format!(
92 " endcase\nendproperty\nassert property (p_reverse_module_{name});\n"
93 ));
94 s
95}
96
97/// A discharged JOURNALED-op injectivity obligation — the hardware twin of the software minimal-bit
98/// journaling (corner 1). A `Journaled` op `F` is many-to-one (it erases), but AUGMENTED with its
99/// Bennett ancilla `J` (the journal entry — the exact bits it destroyed), the map `x ↦ (F(x), J(x))` is
100/// INJECTIVE: the reverse module reconstructs `x` from the post-state AND the ancilla. This closes the
101/// R4 gap — the Z3/exhaustive proofs covered the `Bijection` core, and this covers every `Journaled`
102/// op the same way.
103#[derive(Debug, Clone)]
104pub struct JournaledInjectivity {
105 pub name: String,
106 pub in_bits: u32,
107 /// The constructive reverse: the packed `(F(x), J(x))` output ↦ the input `x`.
108 pub reverse: std::collections::HashMap<usize, usize>,
109 /// The SVA property asserting the ancilla-augmented map is injective over every input.
110 pub sva_property: String,
111}
112
113/// Prove a `Journaled` op is INJECTIVE once augmented with its Bennett ancilla. `augmented[x]` is the
114/// packed `(F(x), J(x))` output for input `x` (all `2^in_bits` inputs present). Injective iff every
115/// packed output is distinct — an EXHAUSTIVE bit-blast, the same rigor as [`certify_gate`], so the
116/// core needs no SMT solver. Fails loud on a collision: two inputs share an augmented output, so the
117/// ancilla did NOT capture the erased bits — a genuine (un-reversible) Landauer erasure.
118pub fn discharge_journaled_injective(
119 name: &str,
120 in_bits: u32,
121 augmented: &[usize],
122) -> Result<JournaledInjectivity, String> {
123 assert_eq!(
124 augmented.len(),
125 1usize << in_bits,
126 "augmented table must cover every input"
127 );
128 let mut reverse = std::collections::HashMap::new();
129 for (x, &y) in augmented.iter().enumerate() {
130 if let Some(&prior) = reverse.get(&y) {
131 return Err(format!(
132 "{name}: NOT injective even with the ancilla — inputs {prior} and {x} both map to augmented output {y}; the ancilla did not capture the erased bits (genuine Landauer erasure)"
133 ));
134 }
135 reverse.insert(y, x);
136 }
137 let sva_property = emit_journaled_injectivity_sva(name, in_bits, augmented);
138 Ok(JournaledInjectivity {
139 name: name.to_string(),
140 in_bits,
141 reverse,
142 sva_property,
143 })
144}
145
146/// Emit the SVA property pinning that the ancilla-augmented map is injective — one case row per input,
147/// each mapping to its distinct augmented output (the reverse module reads it right-to-left).
148fn emit_journaled_injectivity_sva(name: &str, n: u32, augmented: &[usize]) -> String {
149 let mut s = String::new();
150 s.push_str(&format!(
151 "// Journaled-op injectivity: (F(x), ancilla(x)) is injective for `{name}` (reverse reconstructs x)\n"
152 ));
153 s.push_str(&format!(
154 "property p_journaled_injective_{name};\n @(posedge clk) case (x)\n"
155 ));
156 for (x, &y) in augmented.iter().enumerate() {
157 s.push_str(&format!(
158 " {n}'d{x}: (aug == {y}); // reverse: {y} -> {x}\n"
159 ));
160 }
161 s.push_str(&format!(
162 " endcase\nendproperty\nassert property (p_journaled_injective_{name});\n"
163 ));
164 s
165}
166
167#[cfg(test)]
168#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/reverse_module/tests.rs"]
169mod tests;