Skip to main content

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;