use logicaffeine_language::ast::logic::{LogicExpr, QuantifierKind, TemporalOperator, ThematicRole, Term};
use logicaffeine_language::token::TokenType;
use logicaffeine_language::Interner;
#[derive(Debug)]
pub struct SynthesizedSva {
pub sva_text: String,
pub body: String,
pub signals: Vec<String>,
pub kind: String,
}
pub fn synthesize_sva_from_spec(spec: &str, clock: &str) -> Result<SynthesizedSva, String> {
let mut last_err: Option<String> = None;
for block in spec_blocks(spec) {
if block.trim().is_empty() {
continue;
}
match synthesize_one(&block, clock) {
Ok(s) => return Ok(s),
Err(e) => last_err = Some(e),
}
}
Err(last_err.unwrap_or_else(|| {
"No hardware property found. Hardware specs are temporal sentences like \
\"Always, if request is high, then grant is high.\""
.to_string()
}))
}
fn spec_blocks(spec: &str) -> Vec<String> {
let mut blocks = Vec::new();
let mut cur = String::new();
for line in spec.lines() {
if line.trim_start().starts_with("##") {
if !cur.trim().is_empty() {
blocks.push(std::mem::take(&mut cur));
}
cur.clear();
} else {
cur.push_str(line);
cur.push('\n');
}
}
if !cur.trim().is_empty() {
blocks.push(cur);
}
if blocks.is_empty() {
blocks.push(spec.to_string());
}
blocks
}
fn synthesize_one(spec: &str, clock: &str) -> Result<SynthesizedSva, String> {
use logicaffeine_language::compile_kripke_with;
use logicaffeine_language::semantics::knowledge_graph::extract_from_kripke_ast;
use super::fol_to_verify::FolTranslator;
use super::sva_to_verify::extract_signal_names;
let (sva_body, signals, fol_signals) = compile_kripke_with(spec, |ast, interner| {
let mut fol_translator = FolTranslator::new(interner, 5);
let fol_result = fol_translator.translate_property(ast);
let fol_sigs = extract_signal_names(&fol_result);
let kg = extract_from_kripke_ast(ast, interner);
let kg_signals: Vec<String> = kg.signals.iter().map(|s| s.name.clone()).collect();
let body = synthesize_from_ast(ast, interner, clock, &fol_sigs);
(body, kg_signals, fol_sigs)
}).map_err(|_e| {
"not a hardware property — I couldn't read a temporal spec here. Try a sentence like \
\"Always, if request is high, then grant is high.\""
.to_string()
})?;
let body = sva_body;
if body.trim() == "0" {
return Err("Not a temporal property: this sentence describes an action or event, \
not a verifiable hardware property. Wrap in a temporal operator \
(e.g., \"Always, ...\") or restructure as a conditional.".to_string());
}
let trimmed = body.trim_start();
let is_reachability_cover = (trimmed.starts_with("s_eventually(") || trimmed.starts_with("cover"))
&& !body.contains("|->")
&& !body.contains("|=>");
let kind = if is_reachability_cover { "cover" } else { "assert" };
let sva_text = format!(
"{} property (@(posedge {}) {});",
kind, clock, body
);
Ok(SynthesizedSva {
sva_text,
body,
signals: if signals.is_empty() { fol_signals } else { signals },
kind: kind.to_string(),
})
}
fn synthesize_from_ast<'a>(
expr: &'a LogicExpr<'a>,
interner: &Interner,
clock: &str,
fol_signals: &[String],
) -> String {
match expr {
LogicExpr::Temporal { operator, body } => {
let inner = synthesize_from_ast(body, interner, clock, fol_signals);
match operator {
TemporalOperator::Always => inner, TemporalOperator::Eventually => format!("s_eventually({})", inner),
TemporalOperator::Next => format!("nexttime({})", inner),
TemporalOperator::BoundedEventually(n) => format!("##[0:{}] {}", n, inner),
_ => inner,
}
}
LogicExpr::Quantifier { kind: QuantifierKind::Universal, body, variable, .. } => {
let var_name = interner.resolve(*variable).to_string();
if var_name.starts_with('w') {
if let LogicExpr::BinaryOp { left, right, op: TokenType::Implies } = body {
if is_accessibility_predicate(left, interner) {
let inner = synthesize_from_ast(right, interner, clock, fol_signals);
if is_next_temporal_predicate(left, interner) {
return format!("nexttime({})", inner);
}
return inner;
}
}
}
synthesize_from_ast(body, interner, clock, fol_signals)
}
LogicExpr::Quantifier { kind: QuantifierKind::Existential, body, variable, .. } => {
let var_name = interner.resolve(*variable).to_string();
if var_name.starts_with('w') {
if let LogicExpr::BinaryOp { left, right, op: TokenType::And } = body {
if is_accessibility_predicate(left, interner) {
return format!("s_eventually({})", synthesize_from_ast(right, interner, clock, fol_signals));
}
}
}
synthesize_from_ast(body, interner, clock, fol_signals)
}
LogicExpr::Quantifier { kind: QuantifierKind::AtMost(n), body, .. } => {
let inner = synthesize_from_ast(body, interner, clock, fol_signals);
if *n == 1 {
format!("$onehot0({})", inner)
} else {
format!("($countones({}) <= {})", inner, n)
}
}
LogicExpr::Quantifier { kind: QuantifierKind::AtLeast(n), body, .. } => {
let inner = synthesize_from_ast(body, interner, clock, fol_signals);
if *n == 1 {
inner } else {
format!("($countones({}) >= {})", inner, n)
}
}
LogicExpr::Quantifier { kind: QuantifierKind::Cardinal(n), body, .. } => {
let inner = synthesize_from_ast(body, interner, clock, fol_signals);
if *n == 1 {
format!("$onehot({})", inner)
} else {
format!("($countones({}) == {})", inner, n)
}
}
LogicExpr::Quantifier { body, .. } => {
synthesize_from_ast(body, interner, clock, fol_signals)
}
LogicExpr::BinaryOp { left, right, op: TokenType::If } => {
let ante = synthesize_from_ast(left, interner, clock, fol_signals);
let cons = synthesize_from_ast(right, interner, clock, fol_signals);
format!("{} |-> {}", ante, cons)
}
LogicExpr::BinaryOp { left, right, op: TokenType::Implies } => {
let ante = synthesize_from_ast(left, interner, clock, fol_signals);
let cons = synthesize_from_ast(right, interner, clock, fol_signals);
if ante == "1" {
cons
} else {
format!("(!({}) || ({}))", ante, cons)
}
}
LogicExpr::BinaryOp { left, right, op: TokenType::And } => {
let l = synthesize_from_ast(left, interner, clock, fol_signals);
let r = synthesize_from_ast(right, interner, clock, fol_signals);
format!("({} && {})", l, r)
}
LogicExpr::BinaryOp { left, right, op: TokenType::Or } => {
let l = synthesize_from_ast(left, interner, clock, fol_signals);
let r = synthesize_from_ast(right, interner, clock, fol_signals);
format!("({} || {})", l, r)
}
LogicExpr::UnaryOp { operand, .. } => {
let inner = synthesize_from_ast(operand, interner, clock, fol_signals);
format!("!({})", inner)
}
LogicExpr::Predicate { name, args, .. } => {
let pred_name = interner.resolve(*name).to_string();
if pred_name.contains("Accessible") || pred_name.contains("Reachable")
|| pred_name.contains("Next_Temporal")
|| pred_name == "Agent" || pred_name == "Theme"
{
return "1".to_string(); }
let arg_name = args.first().map(|a| term_to_string_helper(a, interner));
if let Some(ref arg) = arg_name {
let candidate = format!("{}_{}_", pred_name, arg);
if let Some(fol_sig) = fol_signals.iter().find(|s| {
s.to_lowercase() == candidate.to_lowercase()
}) {
return fol_sig.clone();
}
}
if let Some(fol_sig) = fol_signals.iter().find(|s| {
let s_lower = s.to_lowercase();
s_lower.contains(&pred_name.to_lowercase())
|| pred_name.to_lowercase().contains(&s_lower)
}) {
fol_sig.clone()
} else {
pred_name.to_lowercase()
}
}
LogicExpr::NeoEvent(data) => {
let verb_name = interner.resolve(data.verb).to_string();
let agent_name = data.roles.iter()
.find(|(role, _)| matches!(role, ThematicRole::Agent))
.map(|(_, term)| term_to_string_helper(term, interner));
let candidate = if let Some(ref arg) = agent_name {
format!("{}_{}_", verb_name, arg)
} else {
verb_name.clone()
};
if let Some(fol_sig) = fol_signals.iter().find(|s| {
s.to_lowercase() == candidate.to_lowercase()
}) {
fol_sig.clone()
} else if let Some(fol_sig) = fol_signals.iter().find(|s| {
let s_lower = s.to_lowercase();
s_lower.contains(&verb_name.to_lowercase())
}) {
fol_sig.clone()
} else {
candidate
}
}
LogicExpr::TemporalBinary { operator, left, right } => {
let l = synthesize_from_ast(left, interner, clock, fol_signals);
let r = synthesize_from_ast(right, interner, clock, fol_signals);
use logicaffeine_language::ast::logic::BinaryTemporalOp;
match operator {
BinaryTemporalOp::Until => format!("({} until {})", l, r),
BinaryTemporalOp::Release => format!("({} release {})", l, r),
BinaryTemporalOp::WeakUntil => format!("({} weak_until {})", l, r),
}
}
LogicExpr::Modal { operand, .. } => {
synthesize_from_ast(operand, interner, clock, fol_signals)
}
LogicExpr::Aspectual { body, .. } => {
synthesize_from_ast(body, interner, clock, fol_signals)
}
LogicExpr::Voice { body, .. } => {
synthesize_from_ast(body, interner, clock, fol_signals)
}
LogicExpr::Relation(data) => {
let verb_name = interner.resolve(data.verb).to_string();
let subj_name = interner.resolve(data.subject.noun).to_string();
let obj_name = interner.resolve(data.object.noun).to_string();
let candidate = format!("{}_{}_", verb_name, subj_name);
if let Some(fol_sig) = fol_signals.iter().find(|s| {
s.to_lowercase() == candidate.to_lowercase()
}) {
fol_sig.clone()
} else if let Some(fol_sig) = fol_signals.iter().find(|s| {
let s_lower = s.to_lowercase();
s_lower.contains(&verb_name.to_lowercase())
|| s_lower.contains(&subj_name.to_lowercase())
|| s_lower.contains(&obj_name.to_lowercase())
}) {
fol_sig.clone()
} else {
format!("{}_{}_", verb_name, obj_name).to_lowercase()
}
}
LogicExpr::Categorical(data) => {
let subj_name = interner.resolve(data.subject.noun).to_string().to_lowercase();
let pred_name = interner.resolve(data.predicate.noun).to_string().to_lowercase();
if data.copula_negative {
format!("({} && !({}))", subj_name, pred_name)
} else {
format!("(!({}) || ({}))", subj_name, pred_name)
}
}
LogicExpr::Scopal { body, .. } => {
synthesize_from_ast(body, interner, clock, fol_signals)
}
LogicExpr::Causal { effect, cause } => {
let e = synthesize_from_ast(effect, interner, clock, fol_signals);
let c = synthesize_from_ast(cause, interner, clock, fol_signals);
format!("({} && {})", c, e)
}
LogicExpr::Concessive { main, .. } => {
synthesize_from_ast(main, interner, clock, fol_signals)
}
LogicExpr::Atom(sym) => {
let name = interner.resolve(*sym).to_string();
if let Some(fol_sig) = fol_signals.iter().find(|s| {
s.to_lowercase() == name.to_lowercase()
}) {
fol_sig.clone()
} else {
name.to_lowercase()
}
}
LogicExpr::Identity { left, right } => {
let l = term_to_string_helper(left, interner).to_lowercase();
let r = term_to_string_helper(right, interner).to_lowercase();
format!("({} == {})", l, r)
}
_ => "0".to_string(),
}
}
fn is_accessibility_predicate<'a>(expr: &'a LogicExpr<'a>, interner: &Interner) -> bool {
if let LogicExpr::Predicate { name, .. } = expr {
let pred_name = interner.resolve(*name).to_string();
pred_name.contains("Accessible") || pred_name.contains("Reachable") || pred_name.contains("Next_Temporal")
} else {
false
}
}
fn is_next_temporal_predicate<'a>(expr: &'a LogicExpr<'a>, interner: &Interner) -> bool {
if let LogicExpr::Predicate { name, .. } = expr {
let pred_name = interner.resolve(*name).to_string();
pred_name.contains("Next_Temporal")
} else {
false
}
}
fn term_to_string_helper<'a>(term: &'a Term<'a>, interner: &Interner) -> String {
match term {
Term::Constant(sym) | Term::Variable(sym) => interner.resolve(*sym).to_string(),
Term::Function(sym, _) => interner.resolve(*sym).to_string(),
_ => "unknown".to_string(),
}
}
#[cfg(test)]
mod block_header_robustness {
use super::*;
#[test]
fn property_followed_by_a_block_synthesizes() {
let spec = "Always, if request then eventually grant.\n## Theorem t:\n It holds.";
let r = synthesize_sva_from_spec(spec, "clk");
assert!(r.is_ok(), "expected SVA, got error: {:?}", r.err());
assert!(r.unwrap().sva_text.contains("property"));
}
#[test]
fn property_inside_a_leading_block_still_works() {
let spec = "## Hardware\nAlways, if request then eventually grant.";
let r = synthesize_sva_from_spec(spec, "clk");
assert!(r.is_ok(), "expected SVA, got error: {:?}", r.err());
}
}