logicaffeine_verify/
multiclock.rs1use crate::ir::VerifyExpr;
12use crate::equivalence::Trace;
13use crate::kinduction;
14use std::collections::{HashMap, HashSet};
15
16#[derive(Debug, Clone)]
17pub struct ClockDomain {
18 pub name: String,
19 pub frequency: Option<u64>,
20 pub ratio: Option<(u32, u32)>,
21}
22
23#[derive(Debug, Clone)]
24pub struct MultiClockModel {
25 pub domains: Vec<ClockDomain>,
26 pub init: VerifyExpr,
27 pub transitions: HashMap<String, VerifyExpr>,
28 pub property: VerifyExpr,
29}
30
31#[derive(Debug)]
32pub enum MultiClockResult {
33 Safe,
34 Unsafe { trace: Trace },
35 Unknown,
36}
37
38fn gcd(a: u32, b: u32) -> u32 {
39 if b == 0 { a } else { gcd(b, a % b) }
40}
41
42fn lcm(a: u32, b: u32) -> u32 {
43 if a == 0 || b == 0 { 1 } else { a / gcd(a, b) * b }
44}
45
46pub fn compute_schedule(domains: &[ClockDomain]) -> Vec<Vec<bool>> {
59 if domains.is_empty() {
60 return vec![vec![]];
61 }
62
63 let rates: Vec<(u32, u32)> = domains.iter().map(|d| {
64 d.ratio.unwrap_or((1, 1))
65 }).collect();
66
67 let common_denom: u32 = rates.iter().fold(1u32, |acc, &(_, d)| lcm(acc, d));
70 let scaled: Vec<u32> = rates.iter().map(|&(n, d)| n * (common_denom / d)).collect();
71 let period = *scaled.iter().max().unwrap_or(&1);
72
73 if period == 0 {
74 return vec![vec![false; domains.len()]];
75 }
76
77 let mut schedule = Vec::with_capacity(period as usize);
78 for step in 0..period {
79 let mut fires = Vec::with_capacity(domains.len());
80 for &fire_count in &scaled {
81 if fire_count == 0 {
82 fires.push(false);
83 } else if fire_count >= period {
84 fires.push(true);
85 } else {
86 let remainder = (step as u64 * fire_count as u64) % period as u64;
89 fires.push(remainder < fire_count as u64);
90 }
91 }
92 schedule.push(fires);
93 }
94
95 schedule
96}
97
98fn extract_next_state_vars(expr: &VerifyExpr) -> HashSet<String> {
102 let mut all_vars = HashSet::new();
103 crate::equivalence::collect_vars_pub(expr, &mut all_vars);
104 let mut bases = HashSet::new();
105 for v in &all_vars {
106 if let Some(base) = v.strip_suffix("@t1") {
107 bases.insert(base.to_string());
108 }
109 }
110 bases
111}
112
113fn build_frame_condition(transition: &VerifyExpr) -> VerifyExpr {
116 let bases = extract_next_state_vars(transition);
117 let mut conditions: Vec<VerifyExpr> = bases.into_iter().map(|base| {
118 VerifyExpr::iff(
119 VerifyExpr::var(format!("{}@t1", base)),
120 VerifyExpr::var(format!("{}@t", base)),
121 )
122 }).collect();
123 conditions.sort_by(|a, b| format!("{:?}", a).cmp(&format!("{:?}", b)));
125 if conditions.is_empty() {
126 VerifyExpr::bool(true)
127 } else {
128 conditions.into_iter().reduce(|a, b| VerifyExpr::and(a, b)).unwrap()
129 }
130}
131
132pub fn verify_multiclock(model: &MultiClockModel, bound: u32) -> MultiClockResult {
139 if model.domains.len() <= 1 {
140 let transition = model.transitions.values().next()
141 .cloned()
142 .unwrap_or(VerifyExpr::bool(true));
143 let result = kinduction::k_induction(&model.init, &transition, &model.property, &[], bound);
144 match result {
145 kinduction::KInductionResult::Proven { .. } => MultiClockResult::Safe,
146 kinduction::KInductionResult::Counterexample { trace, .. } => MultiClockResult::Unsafe { trace },
147 _ => MultiClockResult::Unknown,
148 }
149 } else {
150 verify_multiclock_interleaved(model, bound)
151 }
152}
153
154fn verify_multiclock_interleaved(model: &MultiClockModel, bound: u32) -> MultiClockResult {
164 let schedule = compute_schedule(&model.domains);
165 let period = schedule.len() as u32;
166
167 let mut frame_conditions: HashMap<String, VerifyExpr> = HashMap::new();
169 for domain in &model.domains {
170 if let Some(trans) = model.transitions.get(&domain.name) {
171 frame_conditions.insert(domain.name.clone(), build_frame_condition(trans));
172 }
173 }
174
175 let mut step_transitions: Vec<VerifyExpr> = Vec::with_capacity(period as usize);
180 for step_idx in 0..period as usize {
181 let mut parts: Vec<VerifyExpr> = Vec::new();
182 for (domain_idx, domain) in model.domains.iter().enumerate() {
183 let fires = schedule.get(step_idx)
184 .and_then(|s| s.get(domain_idx))
185 .copied()
186 .unwrap_or(true);
187 if fires {
188 if let Some(trans) = model.transitions.get(&domain.name) {
189 parts.push(trans.clone());
190 }
191 } else {
192 if let Some(frame) = frame_conditions.get(&domain.name) {
193 parts.push(frame.clone());
194 }
195 }
196 }
197 let combined = if parts.is_empty() {
198 VerifyExpr::bool(true)
199 } else {
200 parts.into_iter().reduce(|a, b| VerifyExpr::and(a, b)).unwrap()
201 };
202 step_transitions.push(combined);
203 }
204
205 k_induction_with_schedule(
209 &model.init,
210 &step_transitions,
211 &model.property,
212 bound,
213 )
214}
215
216fn k_induction_with_schedule(
221 init: &VerifyExpr,
222 step_transitions: &[VerifyExpr],
223 property: &VerifyExpr,
224 max_k: u32,
225) -> MultiClockResult {
226 use z3::{SatResult, ast::Bool};
227
228 let period = step_transitions.len() as u32;
229 if period == 0 {
230 return MultiClockResult::Safe;
231 }
232
233
234 for k in 1..=max_k {
235 let base_result = {
238 let solver = crate::solver::new_solver();
239
240 let init_0 = kinduction::instantiate_at(init, 0);
241 let init_z3 = encode_to_bool_mc(&init_0);
242 solver.assert(&init_z3);
243
244 for t in 0..k.saturating_sub(1) {
245 let sched_idx = (t % period) as usize;
246 let trans = kinduction::instantiate_transition(&step_transitions[sched_idx], t);
247 let trans_z3 = encode_to_bool_mc(&trans);
248 solver.assert(&trans_z3);
249 }
250
251 let mut not_props: Vec<Bool> = Vec::new();
252 for t in 0..k {
253 let prop_t = kinduction::instantiate_at(property, t);
254 let prop_z3 = encode_to_bool_mc(&prop_t);
255 not_props.push(prop_z3.not());
256 }
257 let not_prop_refs: Vec<&Bool> = not_props.iter().collect();
258 let some_violation = Bool::or(¬_prop_refs);
259 solver.assert(&some_violation);
260
261 solver.check()
262 };
263
264 match base_result {
265 SatResult::Sat => {
266 return MultiClockResult::Unsafe {
267 trace: Trace { cycles: vec![] },
268 };
269 }
270 SatResult::Unknown => return MultiClockResult::Unknown,
271 SatResult::Unsat => {}
272 }
273
274 let step_result = {
277 let solver = crate::solver::new_solver();
278
279 for t in 0..k {
280 let prop_t = kinduction::instantiate_at(property, t);
281 let prop_z3 = encode_to_bool_mc(&prop_t);
282 solver.assert(&prop_z3);
283 }
284
285 for t in 0..k {
286 let sched_idx = (t % period) as usize;
287 let trans = kinduction::instantiate_transition(&step_transitions[sched_idx], t);
288 let trans_z3 = encode_to_bool_mc(&trans);
289 solver.assert(&trans_z3);
290 }
291
292 let prop_k = kinduction::instantiate_at(property, k);
293 let prop_k_z3 = encode_to_bool_mc(&prop_k);
294 solver.assert(&prop_k_z3.not());
295
296 solver.check()
297 };
298
299 match step_result {
300 SatResult::Unsat => {
301 return MultiClockResult::Safe;
302 }
303 SatResult::Unknown => return MultiClockResult::Unknown,
304 SatResult::Sat => {}
305 }
306 }
307
308 MultiClockResult::Unknown
309}
310
311fn encode_to_bool_mc(expr: &VerifyExpr) -> z3::ast::Bool {
313 let mut all_vars = HashSet::new();
314 crate::equivalence::collect_vars_pub(expr, &mut all_vars);
315
316 let mut bool_vars: HashMap<String, z3::ast::Bool> = HashMap::new();
317 let mut int_vars: HashMap<String, z3::ast::Int> = HashMap::new();
318
319 for name in &all_vars {
320 bool_vars.insert(name.clone(), z3::ast::Bool::new_const(name.as_str()));
321 }
322 crate::equivalence::collect_int_vars_pub(expr, &mut int_vars);
323
324 kinduction::encode_expr_bool(expr, &bool_vars, &int_vars)
325}