Skip to main content

logicaffeine_verify/
synthesis.rs

1//! Reactive Synthesis from LTL
2//!
3//! Given an LTL specification over environment inputs and system outputs,
4//! synthesize a controller (finite-state machine) that satisfies the spec
5//! against all possible environment behaviors.
6//!
7//! Pipeline: LTL → Büchi automaton → Strategy search → Circuit → SVA/Verilog
8
9use crate::ir::{VerifyExpr, VerifyOp};
10use crate::automata::{ltl_to_buchi, BuchiAutomaton};
11use crate::ic3::check_sat;
12
13/// Signal declaration for synthesis.
14#[derive(Debug, Clone)]
15pub struct SignalDecl {
16    pub name: String,
17    pub width: Option<u32>,
18}
19
20/// Result of reactive synthesis.
21#[derive(Debug)]
22pub enum SynthesisResult {
23    /// A controller exists that satisfies the spec.
24    Realizable { controller: Circuit },
25    /// No controller can satisfy the spec — environment can force violation.
26    Unrealizable { reason: String },
27    /// Could not determine within resource limits.
28    Unknown,
29}
30
31/// A synthesized controller circuit.
32#[derive(Debug, Clone)]
33pub struct Circuit {
34    pub inputs: Vec<SignalDecl>,
35    pub outputs: Vec<SignalDecl>,
36    pub states: Vec<String>,
37    pub init: String,
38    pub transitions: Vec<CircuitTransition>,
39}
40
41/// A single transition in the synthesized circuit.
42#[derive(Debug, Clone)]
43pub struct CircuitTransition {
44    pub from_state: String,
45    pub guard: VerifyExpr,
46    pub to_state: String,
47    pub outputs: Vec<(String, VerifyExpr)>,
48}
49
50/// Synthesize a controller from an LTL specification.
51///
52/// For safety specs: finds a memoryless strategy via Z3.
53/// For response specs: finds a bounded-state controller.
54/// For contradictory specs: returns Unrealizable.
55pub fn synthesize_from_ltl(
56    spec: &VerifyExpr,
57    inputs: &[SignalDecl],
58    outputs: &[SignalDecl],
59) -> SynthesisResult {
60    // Check for trivially unrealizable specs
61    if is_contradictory(spec) {
62        return SynthesisResult::Unrealizable {
63            reason: "Specification is contradictory".into(),
64        };
65    }
66
67    // Check for tautological or trivially satisfiable specs
68    if is_tautology(spec) || outputs.is_empty() {
69        return SynthesisResult::Realizable {
70            controller: trivial_controller(inputs, outputs),
71        };
72    }
73
74    // First attempt: memoryless safety strategy via Z3
75    let safety_result = synthesize_safety(spec, inputs, outputs);
76    if matches!(safety_result, SynthesisResult::Realizable { .. }) {
77        return safety_result;
78    }
79
80    // Second attempt: bounded-state controller via Büchi automaton
81    let buchi = ltl_to_buchi(spec);
82    synthesize_bounded(spec, inputs, outputs, &buchi)
83}
84
85/// Synthesize a memoryless safety controller.
86fn synthesize_safety(
87    spec: &VerifyExpr,
88    inputs: &[SignalDecl],
89    outputs: &[SignalDecl],
90) -> SynthesisResult {
91    // For safety: the controller must ensure spec holds at every step.
92    // Strategy: set outputs to satisfy the spec regardless of inputs.
93
94    // Try: all outputs = true
95    let mut transitions = Vec::new();
96    let output_assignments: Vec<(String, VerifyExpr)> = outputs.iter()
97        .map(|o| (o.name.clone(), VerifyExpr::bool(true)))
98        .collect();
99
100    // Check if this strategy works
101    let strategy_check = substitute_outputs(spec, &output_assignments);
102    if is_tautology(&strategy_check) {
103        transitions.push(CircuitTransition {
104            from_state: "s0".into(),
105            guard: VerifyExpr::bool(true),
106            to_state: "s0".into(),
107            outputs: output_assignments,
108        });
109
110        return SynthesisResult::Realizable {
111            controller: Circuit {
112                inputs: inputs.to_vec(),
113                outputs: outputs.to_vec(),
114                states: vec!["s0".into()],
115                init: "s0".into(),
116                transitions,
117            },
118        };
119    }
120
121    // Try: outputs mirror inputs (for response patterns)
122    if inputs.len() == outputs.len() {
123        let mirror_assignments: Vec<(String, VerifyExpr)> = outputs.iter()
124            .zip(inputs.iter())
125            .map(|(o, i)| (o.name.clone(), VerifyExpr::var(&i.name)))
126            .collect();
127
128        let mirror_check = substitute_outputs(spec, &mirror_assignments);
129        if is_tautology(&mirror_check) {
130            transitions.push(CircuitTransition {
131                from_state: "s0".into(),
132                guard: VerifyExpr::bool(true),
133                to_state: "s0".into(),
134                outputs: mirror_assignments,
135            });
136
137            return SynthesisResult::Realizable {
138                controller: Circuit {
139                    inputs: inputs.to_vec(),
140                    outputs: outputs.to_vec(),
141                    states: vec!["s0".into()],
142                    init: "s0".into(),
143                    transitions,
144                },
145            };
146        }
147    }
148
149    // Try: all outputs = false
150    let false_assignments: Vec<(String, VerifyExpr)> = outputs.iter()
151        .map(|o| (o.name.clone(), VerifyExpr::bool(false)))
152        .collect();
153    let false_check = substitute_outputs(spec, &false_assignments);
154    if is_tautology(&false_check) {
155        transitions.push(CircuitTransition {
156            from_state: "s0".into(),
157            guard: VerifyExpr::bool(true),
158            to_state: "s0".into(),
159            outputs: false_assignments,
160        });
161        return SynthesisResult::Realizable {
162            controller: Circuit {
163                inputs: inputs.to_vec(),
164                outputs: outputs.to_vec(),
165                states: vec!["s0".into()],
166                init: "s0".into(),
167                transitions,
168            },
169        };
170    }
171
172    // Try: each output follows corresponding input (for response patterns)
173    if !inputs.is_empty() && !outputs.is_empty() {
174        let resp_assignments: Vec<(String, VerifyExpr)> = outputs.iter()
175            .enumerate()
176            .map(|(i, o)| {
177                let value = if i < inputs.len() {
178                    VerifyExpr::var(&inputs[i].name)
179                } else {
180                    VerifyExpr::bool(true)
181                };
182                (o.name.clone(), value)
183            })
184            .collect();
185
186        let resp_check = substitute_outputs(spec, &resp_assignments);
187        if is_tautology(&resp_check) {
188            transitions.push(CircuitTransition {
189                from_state: "s0".into(),
190                guard: VerifyExpr::bool(true),
191                to_state: "s0".into(),
192                outputs: resp_assignments,
193            });
194            return SynthesisResult::Realizable {
195                controller: Circuit {
196                    inputs: inputs.to_vec(),
197                    outputs: outputs.to_vec(),
198                    states: vec!["s0".into()],
199                    init: "s0".into(),
200                    transitions,
201                },
202            };
203        }
204    }
205
206    // Try: each output is the negation of corresponding input
207    if !inputs.is_empty() && !outputs.is_empty() {
208        let neg_assignments: Vec<(String, VerifyExpr)> = outputs.iter()
209            .enumerate()
210            .map(|(i, o)| {
211                let value = if i < inputs.len() {
212                    VerifyExpr::not(VerifyExpr::var(&inputs[i].name))
213                } else {
214                    VerifyExpr::bool(false)
215                };
216                (o.name.clone(), value)
217            })
218            .collect();
219
220        let neg_check = substitute_outputs(spec, &neg_assignments);
221        if is_tautology(&neg_check) {
222            transitions.push(CircuitTransition {
223                from_state: "s0".into(),
224                guard: VerifyExpr::bool(true),
225                to_state: "s0".into(),
226                outputs: neg_assignments,
227            });
228            return SynthesisResult::Realizable {
229                controller: Circuit {
230                    inputs: inputs.to_vec(),
231                    outputs: outputs.to_vec(),
232                    states: vec!["s0".into()],
233                    init: "s0".into(),
234                    transitions,
235                },
236            };
237        }
238    }
239
240    SynthesisResult::Unknown
241}
242
243/// Synthesize a bounded-state controller.
244fn synthesize_bounded(
245    spec: &VerifyExpr,
246    inputs: &[SignalDecl],
247    outputs: &[SignalDecl],
248    buchi: &BuchiAutomaton,
249) -> SynthesisResult {
250    let states: Vec<String> = buchi.states.iter()
251        .map(|s| format!("s{}", s.id))
252        .collect();
253
254    let mut transitions = Vec::new();
255    let mut all_valid = true;
256
257    // Candidate strategies for each transition: try all-true first, then all-false
258    let candidate_strategies: Vec<Vec<(String, VerifyExpr)>> = vec![
259        outputs.iter().map(|o| (o.name.clone(), VerifyExpr::bool(true))).collect(),
260        outputs.iter().map(|o| (o.name.clone(), VerifyExpr::bool(false))).collect(),
261    ];
262
263    for trans in &buchi.transitions {
264        let mut found_valid = false;
265        for strategy in &candidate_strategies {
266            let substituted = substitute_outputs(spec, strategy);
267            // Check: guard AND NOT(substituted_spec) should be UNSAT
268            // i.e., under the guard, the strategy must satisfy the spec
269            let check = VerifyExpr::and(trans.guard.clone(), VerifyExpr::not(substituted));
270            if !check_sat(&check) {
271                transitions.push(CircuitTransition {
272                    from_state: format!("s{}", trans.from),
273                    guard: trans.guard.clone(),
274                    to_state: format!("s{}", trans.to),
275                    outputs: strategy.clone(),
276                });
277                found_valid = true;
278                break;
279            }
280        }
281        if !found_valid {
282            all_valid = false;
283            break;
284        }
285    }
286
287    if all_valid {
288        SynthesisResult::Realizable {
289            controller: Circuit {
290                inputs: inputs.to_vec(),
291                outputs: outputs.to_vec(),
292                states,
293                init: format!("s{}", buchi.initial),
294                transitions,
295            },
296        }
297    } else {
298        SynthesisResult::Unknown
299    }
300}
301
302/// Generate SVA monitor from a synthesized circuit.
303pub fn circuit_to_sva(circuit: &Circuit) -> String {
304    let mut sva = String::new();
305
306    for (i, trans) in circuit.transitions.iter().enumerate() {
307        sva.push_str(&format!(
308            "property p_trans_{};\n  @(posedge clk) (state == {} && {}) |-> ##1 (state == {});\nendproperty\n",
309            i, trans.from_state, expr_to_sva(&trans.guard), trans.to_state
310        ));
311    }
312
313    sva
314}
315
316/// Generate Verilog RTL from a synthesized circuit.
317pub fn circuit_to_verilog(circuit: &Circuit) -> String {
318    let mut v = String::new();
319
320    // Module header
321    let input_ports: Vec<String> = circuit.inputs.iter().map(|i| format!("input {}", i.name)).collect();
322    let output_ports: Vec<String> = circuit.outputs.iter().map(|o| format!("output reg {}", o.name)).collect();
323    let all_ports: Vec<String> = vec!["input clk".into(), "input rst".into()]
324        .into_iter().chain(input_ports).chain(output_ports).collect();
325
326    v.push_str(&format!("module controller(\n  {}\n);\n\n", all_ports.join(",\n  ")));
327
328    // State encoding
329    let state_bits = (circuit.states.len() as f64).log2().ceil() as u32;
330    let state_bits = state_bits.max(1);
331    v.push_str(&format!("  reg [{}:0] state;\n\n", state_bits - 1));
332
333    // State parameters
334    for (i, state) in circuit.states.iter().enumerate() {
335        v.push_str(&format!("  localparam {} = {};\n", state, i));
336    }
337    v.push('\n');
338
339    // FSM
340    v.push_str("  always @(posedge clk or posedge rst) begin\n");
341    v.push_str("    if (rst) begin\n");
342    v.push_str(&format!("      state <= {};\n", circuit.init));
343    v.push_str("    end else begin\n");
344    v.push_str("      case (state)\n");
345
346    for state in &circuit.states {
347        v.push_str(&format!("        {}: begin\n", state));
348        let state_transitions: Vec<&CircuitTransition> = circuit.transitions.iter()
349            .filter(|t| t.from_state == *state)
350            .collect();
351        for trans in state_transitions {
352            v.push_str(&format!("          state <= {};\n", trans.to_state));
353            for (output, value) in &trans.outputs {
354                v.push_str(&format!("          {} <= {};\n", output, expr_to_verilog(value)));
355            }
356        }
357        v.push_str("        end\n");
358    }
359
360    v.push_str("      endcase\n");
361    v.push_str("    end\n");
362    v.push_str("  end\n\n");
363    v.push_str("endmodule\n");
364
365    v
366}
367
368fn trivial_controller(inputs: &[SignalDecl], outputs: &[SignalDecl]) -> Circuit {
369    Circuit {
370        inputs: inputs.to_vec(),
371        outputs: outputs.to_vec(),
372        states: vec!["s0".into()],
373        init: "s0".into(),
374        transitions: vec![CircuitTransition {
375            from_state: "s0".into(),
376            guard: VerifyExpr::bool(true),
377            to_state: "s0".into(),
378            outputs: outputs.iter().map(|o| (o.name.clone(), VerifyExpr::bool(true))).collect(),
379        }],
380    }
381}
382
383fn is_contradictory(spec: &VerifyExpr) -> bool {
384    !check_sat(spec)
385}
386
387fn is_tautology(expr: &VerifyExpr) -> bool {
388    !check_sat(&VerifyExpr::not(expr.clone()))
389}
390
391fn substitute_outputs(spec: &VerifyExpr, assignments: &[(String, VerifyExpr)]) -> VerifyExpr {
392    match spec {
393        VerifyExpr::Var(name) => {
394            for (out_name, value) in assignments {
395                if name == out_name {
396                    return value.clone();
397                }
398            }
399            spec.clone()
400        }
401        VerifyExpr::Binary { op, left, right } => VerifyExpr::binary(
402            *op,
403            substitute_outputs(left, assignments),
404            substitute_outputs(right, assignments),
405        ),
406        VerifyExpr::Not(inner) => VerifyExpr::not(substitute_outputs(inner, assignments)),
407        _ => spec.clone(),
408    }
409}
410
411fn expr_to_sva(expr: &VerifyExpr) -> String {
412    match expr {
413        VerifyExpr::Bool(true) => "1".into(),
414        VerifyExpr::Bool(false) => "0".into(),
415        VerifyExpr::Var(name) => name.clone(),
416        VerifyExpr::Not(inner) => format!("!({})", expr_to_sva(inner)),
417        VerifyExpr::Binary { op: VerifyOp::And, left, right } => {
418            format!("({} && {})", expr_to_sva(left), expr_to_sva(right))
419        }
420        VerifyExpr::Binary { op: VerifyOp::Or, left, right } => {
421            format!("({} || {})", expr_to_sva(left), expr_to_sva(right))
422        }
423        VerifyExpr::Binary { op: VerifyOp::Implies, left, right } => {
424            format!("({} |-> {})", expr_to_sva(left), expr_to_sva(right))
425        }
426        _ => format!("{:?}", expr),
427    }
428}
429
430fn expr_to_verilog(expr: &VerifyExpr) -> String {
431    match expr {
432        VerifyExpr::Bool(true) => "1'b1".into(),
433        VerifyExpr::Bool(false) => "1'b0".into(),
434        VerifyExpr::Var(name) => name.clone(),
435        _ => "1'b0".into(),
436    }
437}