use serde::{Deserialize, Serialize};
use crate::hardware::HardwareInfo;
use crate::knowledge::{KnowledgePack, Microarch};
use crate::Result;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArchSide {
pub microarch: String,
pub pack_name: String,
pub brand: String,
pub fingerprint: Option<String>,
pub threads: usize,
pub cores: usize,
pub domains: usize,
pub l1d_bytes: u64,
pub l2_bytes: u64,
pub l3_bytes: u64,
pub provenance: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArchCompareReport {
pub a: ArchSide,
pub b: ArchSide,
pub differences: Vec<String>,
pub notes: Vec<String>,
pub research_status: String,
}
pub fn side_from_info(info: &HardwareInfo, packs: &KnowledgePack) -> Result<ArchSide> {
let micro = match &info.support {
crate::hardware::SupportStatus::Supported { microarch, .. } => *microarch,
_ => {
return Err(crate::SiliceraError::UnsupportedCpu(
"architecture compare requires a supported Zen side".into(),
))
}
};
let pack = packs
.packs
.iter()
.find(|p| p.microarch == micro)
.ok_or_else(|| {
crate::SiliceraError::Parse(format!("no knowledge pack for {}", micro.tag()))
})?;
let l3: u64 = info
.topology
.packages
.iter()
.flat_map(|p| p.domains.iter())
.filter_map(|d| d.l3_bytes())
.sum();
Ok(ArchSide {
microarch: micro.tag().into(),
pack_name: pack.name.clone(),
brand: info.brand.clone(),
fingerprint: info.fingerprint.as_ref().map(|f| f.value.clone()),
threads: info.topology.thread_count(),
cores: info.topology.core_count(),
domains: info.topology.domain_count(),
l1d_bytes: info.topology.typical_l1d_bytes(),
l2_bytes: info.topology.typical_l2_bytes(),
l3_bytes: l3,
provenance: pack.provenance.clone(),
})
}
pub fn compare_architectures(a: ArchSide, b: ArchSide) -> ArchCompareReport {
let mut differences = Vec::new();
if a.microarch != b.microarch {
differences.push(format!(
"microarchitecture: {} vs {}",
a.microarch, b.microarch
));
}
if a.domains != b.domains {
differences.push(format!("compute domains (CCD): {} vs {}", a.domains, b.domains));
}
if a.cores != b.cores {
differences.push(format!("cores: {} vs {}", a.cores, b.cores));
}
if a.threads != b.threads {
differences.push(format!("threads: {} vs {}", a.threads, b.threads));
}
if a.l1d_bytes != b.l1d_bytes {
differences.push(format!("typical L1D: {} vs {} bytes", a.l1d_bytes, b.l1d_bytes));
}
if a.l2_bytes != b.l2_bytes {
differences.push(format!("typical L2: {} vs {} bytes", a.l2_bytes, b.l2_bytes));
}
if a.l3_bytes != b.l3_bytes {
differences.push(format!("aggregate L3: {} vs {} bytes", a.l3_bytes, b.l3_bytes));
}
let notes = vec![
"This report compares knowledge-pack expectations and discovered topology.".into(),
"It does NOT claim which microarchitecture is faster.".into(),
"Strategy divergence requires measured HNEPs from each class (Silicon Split).".into(),
];
let research_status = if differences.is_empty() {
"STRUCTURALLY SIMILAR — still measure; topology equality ≠ identical winners".into()
} else {
format!(
"{} structural differences recorded — ready for dual-SKU measurement when hosts exist",
differences.len()
)
};
ArchCompareReport {
a,
b,
differences,
notes,
research_status,
}
}
pub fn compare_mock_zen4_zen5(packs: &KnowledgePack) -> Result<ArchCompareReport> {
use crate::hardware::{HardwareBackend, MockHardware};
let a = MockHardware::zen4_single_ccd().discover(packs)?;
let b = MockHardware::zen5_dual_ccd().discover(packs)?;
Ok(compare_architectures(
side_from_info(&a, packs)?,
side_from_info(&b, packs)?,
))
}
pub fn parse_microarch_tag(s: &str) -> Option<Microarch> {
match s.to_ascii_lowercase().as_str() {
"zen3" => Some(Microarch::Zen3),
"zen4" => Some(Microarch::Zen4),
"zen5" => Some(Microarch::Zen5),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mock_zen4_vs_zen5_differs() {
let packs = KnowledgePack::builtin();
let report = compare_mock_zen4_zen5(&packs).unwrap();
assert!(!report.differences.is_empty());
assert_eq!(report.a.microarch, "ZEN4");
assert_eq!(report.b.microarch, "ZEN5");
}
}