use spdr::{
Expo, ExpoProfile, Millivolts, Picoseconds, RatedTimings, Xmp, XmpProfile, decode_expo,
decode_xmp,
};
const FIXTURE: &[u8] = include_bytes!("fixtures/teamgroup-ud5-6000_0104eef6.spd");
const XMP_HEADER_CRC: u16 = 0x252C;
const XMP_PROFILE1_CRC: u16 = 0x0A5F;
const XMP_PROFILE2_CRC: u16 = 0x0AC4;
const EXPO_BLOCK_CRC: u16 = 0x9FE2;
fn xmp() -> Xmp<'static> {
decode_xmp(FIXTURE).expect("fixture is full length")
}
fn expo() -> Expo {
decode_expo(FIXTURE).expect("fixture is full length")
}
#[test]
fn xmp_section_crcs_match_stored() {
let Xmp::Present {
header_crc,
profile1,
profile2,
} = xmp()
else {
panic!("the fixture carries the XMP 3.0 magic");
};
assert!(header_crc.matches, "XMP header CRC: {header_crc:?}");
assert_eq!(header_crc.computed, XMP_HEADER_CRC);
assert_eq!(header_crc.stored, XMP_HEADER_CRC);
let p1 = profile1.expect("profile 1 enabled");
assert!(p1.crc.matches, "XMP profile 1 CRC: {:?}", p1.crc);
assert_eq!(p1.crc.computed, XMP_PROFILE1_CRC);
assert_eq!(p1.crc.stored, XMP_PROFILE1_CRC);
let p2 = profile2.expect("profile 2 enabled");
assert!(p2.crc.matches, "XMP profile 2 CRC: {:?}", p2.crc);
assert_eq!(p2.crc.computed, XMP_PROFILE2_CRC);
assert_eq!(p2.crc.stored, XMP_PROFILE2_CRC);
}
#[test]
fn expo_block_crc_matches_stored() {
let Expo::Present { block_crc, .. } = expo() else {
panic!("the fixture carries the EXPO magic");
};
assert!(block_crc.matches, "EXPO block CRC: {block_crc:?}");
assert_eq!(block_crc.computed, EXPO_BLOCK_CRC);
assert_eq!(block_crc.stored, EXPO_BLOCK_CRC);
}
#[test]
fn xmp_profile1_is_rated_ddr5_6000_38_38_38_78_1v25() {
let p = present_xmp_profile1();
assert_eq!(p.name, Some("TG-6000-38-38-78"));
assert_rated_6000_38_38_38_78_1v25(&p.rated);
}
#[test]
fn expo_profile1_is_rated_ddr5_6000_38_38_38_78_1v25() {
let p = present_expo_profile1();
assert_rated_6000_38_38_38_78_1v25(&p.rated);
}
#[test]
fn xmp_profile2_is_rated_ddr5_5600_40_40_84() {
let Xmp::Present { profile2, .. } = xmp() else {
panic!("XMP present");
};
let p = profile2.expect("profile 2 enabled");
assert_eq!(p.name, Some("TG-5600-40-40-84"));
assert_eq!(p.rated.data_rate_mt_s, 5600);
assert_eq!(p.rated.cas_latency, 40);
assert_eq!(clock_counts(&p.rated), (40, 40, 84));
}
#[test]
fn expo_profile2_is_rated_ddr5_5600_40_40_84() {
let Expo::Present { profile2, .. } = expo() else {
panic!("EXPO present");
};
let p = profile2.expect("profile 2 populated");
assert_eq!(p.rated.data_rate_mt_s, 5600);
assert_eq!(p.rated.cas_latency, 40);
assert_eq!(clock_counts(&p.rated), (40, 40, 84));
}
#[test]
fn xmp_and_expo_profile1_agree_field_by_field() {
let a = present_xmp_profile1().rated;
let b = present_expo_profile1().rated;
assert_eq!(a, b);
}
#[test]
fn absent_profiles_on_input_without_magic() {
let blank = [0u8; 1024];
assert_eq!(decode_xmp(&blank).unwrap(), Xmp::Absent);
assert_eq!(decode_expo(&blank).unwrap(), Expo::Absent);
}
fn present_xmp_profile1() -> XmpProfile<'static> {
let Xmp::Present { profile1, .. } = xmp() else {
panic!("XMP present");
};
profile1.expect("profile 1 enabled")
}
fn present_expo_profile1() -> ExpoProfile {
let Expo::Present { profile1, .. } = expo() else {
panic!("EXPO present");
};
profile1.expect("profile 1 populated")
}
fn clock_counts(r: &RatedTimings) -> (u32, u32, u32) {
let tck = r.cycle_time.picoseconds();
let cycles = |t: Picoseconds| (t.picoseconds() + tck / 2) / tck;
(cycles(r.trcd), cycles(r.trp), cycles(r.tras))
}
fn assert_rated_6000_38_38_38_78_1v25(r: &RatedTimings) {
assert_eq!(r.cycle_time, Picoseconds(333));
assert_eq!(r.data_rate_mt_s, 6000);
assert_eq!(r.cas_latency, 38);
assert_eq!(r.trcd, Picoseconds(12654));
assert_eq!(r.trp, Picoseconds(12654));
assert_eq!(r.tras, Picoseconds(25974));
assert_eq!(clock_counts(r), (38, 38, 78));
assert_eq!(r.vdd, Millivolts(1250));
assert_eq!(r.vddq, Millivolts(1250));
assert_eq!(r.vpp, Millivolts(1800));
}