use super::{SvaProperty, SvaAssertionKind};
use super::rtl_extract::RtlModule;
#[derive(Debug, Clone)]
pub struct CdcClockDomain {
pub name: String,
pub clock_signal: String,
}
#[derive(Debug, Clone, PartialEq)]
pub enum CdcPattern {
TwoFlopSync { source_domain: String, dest_domain: String },
ThreeFlopSync { source_domain: String, dest_domain: String },
GrayCode { width: u32 },
HandshakeCdc { req: String, ack: String },
PulseSynchronizer,
AsyncFifo,
}
#[derive(Debug, Clone)]
pub struct CdcCrossing {
pub signal: String,
pub source_domain: String,
pub dest_domain: String,
pub pattern: Option<CdcPattern>,
pub safe: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CdcViolationType {
MissingSynchronizer,
Reconvergence,
GlitchRisk,
BusWithoutEncoding,
}
#[derive(Debug, Clone)]
pub struct CdcViolation {
pub signal: String,
pub source_domain: String,
pub dest_domain: String,
pub violation_type: CdcViolationType,
pub message: String,
}
#[derive(Debug, Clone)]
pub struct CdcReport {
pub crossings: Vec<CdcCrossing>,
pub violations: Vec<CdcViolation>,
pub patterns: Vec<CdcPattern>,
}
pub fn analyze_cdc(
rtl: &RtlModule,
domains: &[CdcClockDomain],
) -> CdcReport {
let mut crossings = Vec::new();
let mut violations = Vec::new();
let mut patterns = Vec::new();
if domains.len() < 2 {
return CdcReport { crossings, violations, patterns };
}
for signal in &rtl.signals {
for i in 0..domains.len() {
for j in (i + 1)..domains.len() {
let src = &domains[i];
let dst = &domains[j];
let in_src = signal.name.contains(&src.name) || is_in_domain(&signal.name, src);
let in_dst = signal.name.contains(&dst.name) || is_in_domain(&signal.name, dst);
if in_src || in_dst {
let pattern = detect_pattern(&signal.name, rtl, src, dst);
let safe = pattern.is_some();
if let Some(ref pat) = pattern {
patterns.push(pat.clone());
}
crossings.push(CdcCrossing {
signal: signal.name.clone(),
source_domain: src.name.clone(),
dest_domain: dst.name.clone(),
pattern: pattern.clone(),
safe,
});
if !safe {
violations.push(CdcViolation {
signal: signal.name.clone(),
source_domain: src.name.clone(),
dest_domain: dst.name.clone(),
violation_type: CdcViolationType::MissingSynchronizer,
message: format!(
"Signal '{}' crosses from '{}' to '{}' without synchronizer",
signal.name, src.name, dst.name
),
});
}
}
}
}
}
for crossing in &crossings {
let sig = rtl.signals.iter().find(|s| s.name == crossing.signal);
if let Some(s) = sig {
if s.width > 1 && crossing.safe {
if let Some(CdcPattern::TwoFlopSync { .. }) = &crossing.pattern {
violations.push(CdcViolation {
signal: crossing.signal.clone(),
source_domain: crossing.source_domain.clone(),
dest_domain: crossing.dest_domain.clone(),
violation_type: CdcViolationType::BusWithoutEncoding,
message: format!(
"Multi-bit signal '{}' (width {}) uses 2-flop sync without gray code",
crossing.signal, s.width
),
});
}
}
}
}
CdcReport { crossings, violations, patterns }
}
pub fn cdc_sva_properties(report: &CdcReport) -> Vec<SvaProperty> {
let mut props = Vec::new();
for crossing in &report.crossings {
if let Some(CdcPattern::TwoFlopSync { ref dest_domain, .. }) = crossing.pattern {
props.push(SvaProperty {
name: format!("cdc_2flop_{}", crossing.signal),
clock: format!("clk_{}", dest_domain),
body: format!(
"{sig}_sync1 |=> {sig}_sync2",
sig = crossing.signal
),
kind: SvaAssertionKind::Assert,
});
}
if let Some(CdcPattern::HandshakeCdc { ref req, ref ack }) = crossing.pattern {
props.push(SvaProperty {
name: format!("cdc_handshake_{}", crossing.signal),
clock: "clk".into(),
body: format!("{} |-> s_eventually({})", req, ack),
kind: SvaAssertionKind::Assert,
});
}
}
props
}
fn is_in_domain(signal_name: &str, domain: &CdcClockDomain) -> bool {
signal_name.starts_with(&format!("{}_", domain.name))
}
fn detect_pattern(
signal_name: &str,
rtl: &RtlModule,
src: &CdcClockDomain,
dst: &CdcClockDomain,
) -> Option<CdcPattern> {
let sync1 = format!("{}_sync1", signal_name);
let sync2 = format!("{}_sync2", signal_name);
let sync3 = format!("{}_sync3", signal_name);
if rtl.signals.iter().any(|s| s.name == sync1)
&& rtl.signals.iter().any(|s| s.name == sync2)
&& rtl.signals.iter().any(|s| s.name == sync3)
{
return Some(CdcPattern::ThreeFlopSync {
source_domain: src.name.clone(),
dest_domain: dst.name.clone(),
});
}
if rtl.signals.iter().any(|s| s.name == sync1) && rtl.signals.iter().any(|s| s.name == sync2) {
return Some(CdcPattern::TwoFlopSync {
source_domain: src.name.clone(),
dest_domain: dst.name.clone(),
});
}
if signal_name.contains("req") {
let ack_name = signal_name.replace("req", "ack");
if rtl.signals.iter().any(|s| s.name == ack_name) {
return Some(CdcPattern::HandshakeCdc {
req: signal_name.into(),
ack: ack_name,
});
}
}
if signal_name.contains("gray") || signal_name.contains("grey") {
let sig = rtl.signals.iter().find(|s| s.name == signal_name)?;
return Some(CdcPattern::GrayCode { width: sig.width });
}
if signal_name.contains("fifo") || signal_name.contains("async_fifo") {
return Some(CdcPattern::AsyncFifo);
}
None
}