logicaffeine_compile/codegen_sva/
sufficiency.rs1use logicaffeine_language::semantics::knowledge_graph::HwKnowledgeGraph;
7use serde::{Serialize, Deserialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct SufficiencyReport {
12 pub lonely_signals: Vec<String>,
13 pub unconstrained_outputs: Vec<String>,
14 pub missing_handshakes: Vec<(String, String)>,
15 pub coverage_ratio: f64,
16 pub recommendations: Vec<String>,
17}
18
19const HANDSHAKE_PAIRS: &[(&str, &[&str])] = &[
21 ("req", &["ack", "gnt", "grant"]),
22 ("request", &["acknowledge", "acknowledgment", "response", "grant"]),
23 ("valid", &["ready", "rdy"]),
24 ("cmd", &["resp", "response"]),
25 ("start", &["done", "complete"]),
26];
27
28pub fn analyze_sufficiency(kg: &HwKnowledgeGraph) -> SufficiencyReport {
30 use logicaffeine_language::semantics::knowledge_graph::SignalRole;
31
32 let signal_names: std::collections::HashSet<String> = kg.signals.iter().map(|s| s.name.clone()).collect();
33 let edge_signals: std::collections::HashSet<String> = kg.edges.iter()
34 .flat_map(|e| vec![e.from.clone(), e.to.clone()])
35 .collect();
36
37 let lonely_signals: Vec<String> = signal_names.iter()
39 .filter(|s| !edge_signals.contains(*s))
40 .cloned()
41 .collect();
42
43 let unconstrained_outputs: Vec<String> = kg.signals.iter()
45 .filter(|s| s.role == SignalRole::Output)
46 .filter(|s| !kg.edges.iter().any(|e| e.to == s.name))
47 .map(|s| s.name.clone())
48 .collect();
49
50 let mut missing_handshakes: Vec<(String, String)> = Vec::new();
52 for (trigger_pattern, response_patterns) in HANDSHAKE_PAIRS {
53 let trigger_match = kg.signals.iter().find(|s| {
54 s.name.to_lowercase().contains(trigger_pattern)
55 });
56 if let Some(trigger_sig) = trigger_match {
57 for resp_pattern in *response_patterns {
58 let resp_match = kg.signals.iter().find(|s| {
59 let lower = s.name.to_lowercase();
60 lower.contains(resp_pattern) && s.name != trigger_sig.name
61 });
62 if let Some(resp_sig) = resp_match {
63 let has_edge = kg.edges.iter().any(|e| {
65 (e.from == trigger_sig.name && e.to == resp_sig.name)
66 || (e.from == resp_sig.name && e.to == trigger_sig.name)
67 });
68 if !has_edge {
69 missing_handshakes.push((trigger_sig.name.clone(), resp_sig.name.clone()));
70 }
71 break; }
73 }
74 }
75 }
76
77 let total = signal_names.len() + kg.edges.len();
79 let covered = edge_signals.len();
80 let coverage_ratio = if total > 0 { covered as f64 / total as f64 } else { 0.0 };
81
82 let mut recommendations = Vec::new();
84 for sig in &lonely_signals {
85 recommendations.push(format!("Signal '{}' is not constrained by any property", sig));
86 }
87 for sig in &unconstrained_outputs {
88 recommendations.push(format!("Output '{}' has no driving property", sig));
89 }
90 for (trigger, response) in &missing_handshakes {
91 recommendations.push(format!("Missing handshake: '{}' has no edge to '{}'", trigger, response));
92 }
93
94 SufficiencyReport {
95 lonely_signals,
96 unconstrained_outputs,
97 missing_handshakes,
98 coverage_ratio,
99 recommendations,
100 }
101}