#[cfg(feature = "frontend")]
use std::collections::HashMap;
use crate::netlist::InstantiatedSignalId;
#[cfg(feature = "frontend")]
mod clock;
#[cfg(feature = "frontend")]
mod dpi;
#[cfg(feature = "frontend")]
pub(crate) mod graph;
#[cfg(feature = "frontend")]
mod inferred_clock;
#[cfg(feature = "frontend")]
pub use dpi::{DpiTagStats, dpi_tag_stats};
#[cfg(feature = "frontend")]
pub use graph::{DriverGraph, Partition, build_driver_graph, partition};
#[cfg(feature = "frontend")]
pub use inferred_clock::{infer_procedural_clock, infer_structural_clock};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct RegionId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ClockDomainId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct CallSiteId(pub u32);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum CdcClassification {
Async,
SameEdgeDifferentClock,
Unknown,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum CdcConfidence {
StaticallyProven,
HeuristicIdiom,
Unproven,
}
#[derive(Clone, Copy, Debug)]
pub struct CdcEdge {
pub from_region: RegionId,
pub to_region: RegionId,
pub signal: InstantiatedSignalId,
pub classification: CdcClassification,
pub confidence: CdcConfidence,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DpiVpiTag {
Pure,
DeferredSafe,
Barrier,
SpeculationUnsafe,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ForceObservedAction {
NoOp,
FenceRegion(RegionId),
FenceCrossRegionWrite,
}
pub trait StaticAnalysisView {
fn partition_of(&self, sig: InstantiatedSignalId) -> RegionId;
fn cdc_edges_into(&self, region: RegionId) -> Vec<CdcEdge>;
fn dpi_tag(&self, call: CallSiteId) -> DpiVpiTag;
fn on_force_observed(&self, sig: InstantiatedSignalId) -> ForceObservedAction;
}
#[derive(Clone, Copy, Debug)]
pub struct ClockDomain {
pub id: ClockDomainId,
pub clock_signal: InstantiatedSignalId,
pub edge: crate::state::EdgeKind,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AnalysisError {
Dataflow(String),
Unsupported(String),
}
impl std::fmt::Display for AnalysisError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AnalysisError::Dataflow(msg) => write!(f, "analysis dataflow error: {msg}"),
AnalysisError::Unsupported(msg) => write!(f, "unsupported construct: {msg}"),
}
}
}
impl std::error::Error for AnalysisError {}
#[derive(Clone, Copy, Debug)]
pub struct AnalysisOptions {
pub cdc_confidence_floor: CdcConfidence,
pub dpi_default: DpiVpiTag,
pub max_partition_merge_passes: u32,
}
impl Default for AnalysisOptions {
fn default() -> Self {
AnalysisOptions {
cdc_confidence_floor: CdcConfidence::StaticallyProven,
dpi_default: DpiVpiTag::Barrier,
max_partition_merge_passes: 64,
}
}
}
#[cfg(feature = "frontend")]
#[derive(Debug, Clone)]
pub struct StaticAnalysisResult {
partitions: Vec<Partition>,
clock_domains: Vec<ClockDomain>,
cdc_edges: Vec<CdcEdge>,
partition_index: HashMap<InstantiatedSignalId, RegionId>,
call_tags: Vec<DpiVpiTag>,
}
#[cfg(feature = "frontend")]
impl StaticAnalysisResult {
pub fn partition(&self, id: RegionId) -> &Partition {
&self.partitions[id.0 as usize]
}
pub fn clock_domains(&self) -> &[ClockDomain] {
&self.clock_domains
}
}
#[cfg(feature = "frontend")]
impl StaticAnalysisView for StaticAnalysisResult {
fn partition_of(&self, sig: InstantiatedSignalId) -> RegionId {
self.partition_index[&sig]
}
fn cdc_edges_into(&self, region: RegionId) -> Vec<CdcEdge> {
self.cdc_edges
.iter()
.filter(|e| e.to_region == region)
.copied()
.collect()
}
fn dpi_tag(&self, call: CallSiteId) -> DpiVpiTag {
self.call_tags[call.0 as usize]
}
fn on_force_observed(&self, _sig: InstantiatedSignalId) -> ForceObservedAction {
ForceObservedAction::NoOp
}
}
#[cfg(feature = "frontend")]
pub fn analyze<'d>(
netlist: &mut crate::netlist::Netlist<'d>,
slang_analysis: &sv_lang::Analysis<'d>,
eval: &sv_lang::EvalSession<'d>,
opts: AnalysisOptions,
) -> Result<StaticAnalysisResult, AnalysisError> {
graph::populate_driver_kinds(netlist, slang_analysis);
let driver_graph = graph::build_driver_graph(netlist, slang_analysis);
let partitions = graph::partition(&driver_graph, opts.max_partition_merge_passes);
let cdc_edges = clock::classify_cdc_edges(&driver_graph, &partitions);
let call_tags = dpi::collect_call_tags(netlist, slang_analysis, eval);
let clock_domains = driver_graph
.clock_domain_registry()
.iter()
.enumerate()
.map(|(i, &(clock_signal, edge))| ClockDomain {
id: ClockDomainId(i as u32),
clock_signal,
edge,
})
.collect();
let mut partition_index = HashMap::new();
for p in &partitions {
for &sig in p.nets.iter().chain(p.processes.iter()) {
partition_index.insert(sig, p.id);
}
}
Ok(StaticAnalysisResult {
partitions,
clock_domains,
cdc_edges,
partition_index,
call_tags,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn analysis_options_default_is_fail_closed() {
const EXPECTED_CDC_FLOOR: CdcConfidence = CdcConfidence::StaticallyProven;
const EXPECTED_DPI_DEFAULT: DpiVpiTag = DpiVpiTag::Barrier;
let opts = AnalysisOptions::default();
assert_eq!(opts.cdc_confidence_floor, EXPECTED_CDC_FLOOR);
assert_eq!(opts.dpi_default, EXPECTED_DPI_DEFAULT);
}
}