use crate::error::DecodeError;
use crate::reader::SpdImage;
use core::fmt;
const OFF_TCKAVG_MIN: usize = 20;
const OFF_TCKAVG_MAX: usize = 22;
const OFF_CAS_LATENCIES: usize = 24; const CAS_LATENCY_BYTES: usize = 5;
const OFF_TAA: usize = 30;
const OFF_TRCD: usize = 32;
const OFF_TRP: usize = 34;
const OFF_TRAS: usize = 36;
const OFF_TRC: usize = 38;
const OFF_TWR: usize = 40;
const OFF_TRFC1: usize = 42;
const OFF_TRFC2: usize = 44;
const OFF_TRFCSB: usize = 46;
const OFF_TRRD_L: usize = 70;
const OFF_TCCD_L: usize = 73;
const OFF_TCCD_L_WR: usize = 76;
const OFF_TCCD_L_WR2: usize = 79;
const OFF_TFAW: usize = 82;
const OFF_TWTR_L: usize = 85;
const OFF_TWTR_S: usize = 88;
const OFF_TRTP: usize = 91;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Picoseconds(pub u32);
impl Picoseconds {
#[must_use]
pub const fn picoseconds(self) -> u32 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ClockCycles(pub u8);
impl ClockCycles {
#[must_use]
pub const fn cycles(self) -> u8 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TimingPair {
pub time: Picoseconds,
pub clocks: ClockCycles,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct CasLatencies(u64);
impl CasLatencies {
const BASE_CL: u16 = 20;
const BIT_COUNT: u32 = (CAS_LATENCY_BYTES * 8) as u32;
#[must_use]
pub fn contains(self, cl: u16) -> bool {
if cl < Self::BASE_CL || (cl - Self::BASE_CL) % 2 != 0 {
return false;
}
let bit = u32::from((cl - Self::BASE_CL) / 2);
bit < Self::BIT_COUNT && (self.0 >> bit) & 1 != 0
}
pub fn iter(self) -> impl Iterator<Item = u16> {
(0..Self::BIT_COUNT).filter_map(move |bit| {
if (self.0 >> bit) & 1 != 0 {
Some(Self::BASE_CL + 2 * bit as u16)
} else {
None
}
})
}
}
impl fmt::Debug for CasLatencies {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for CasLatencies {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_seq(self.iter())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Timings {
pub tckavg_min: Picoseconds,
pub tckavg_max: Picoseconds,
pub supported_cas_latencies: CasLatencies,
pub taa: Picoseconds,
pub trcd: Picoseconds,
pub trp: Picoseconds,
pub tras: Picoseconds,
pub trc: Picoseconds,
pub twr: Picoseconds,
pub trfc1: Picoseconds,
pub trfc2: Picoseconds,
pub trfcsb: Picoseconds,
pub trrd_l: TimingPair,
pub tccd_l: TimingPair,
pub tccd_l_wr: TimingPair,
pub tccd_l_wr2: TimingPair,
pub tfaw: TimingPair,
pub twtr_l: TimingPair,
pub twtr_s: TimingPair,
pub trtp: TimingPair,
}
impl Timings {
#[must_use]
pub fn base_data_rate_mt_s(&self) -> u32 {
data_rate_mt_s(self.tckavg_min)
}
}
pub fn decode_timings(bytes: &[u8]) -> Result<Timings, DecodeError> {
let spd = SpdImage::new(bytes);
Ok(Timings {
tckavg_min: ps_units(read_le_u16(&spd, OFF_TCKAVG_MIN)?),
tckavg_max: ps_units(read_le_u16(&spd, OFF_TCKAVG_MAX)?),
supported_cas_latencies: decode_cas_latencies(&spd)?,
taa: ps_units(read_le_u16(&spd, OFF_TAA)?),
trcd: ps_units(read_le_u16(&spd, OFF_TRCD)?),
trp: ps_units(read_le_u16(&spd, OFF_TRP)?),
tras: ps_units(read_le_u16(&spd, OFF_TRAS)?),
trc: ps_units(read_le_u16(&spd, OFF_TRC)?),
twr: ps_units(read_le_u16(&spd, OFF_TWR)?),
trfc1: ns_units(read_le_u16(&spd, OFF_TRFC1)?),
trfc2: ns_units(read_le_u16(&spd, OFF_TRFC2)?),
trfcsb: ns_units(read_le_u16(&spd, OFF_TRFCSB)?),
trrd_l: read_pair(&spd, OFF_TRRD_L)?,
tccd_l: read_pair(&spd, OFF_TCCD_L)?,
tccd_l_wr: read_pair(&spd, OFF_TCCD_L_WR)?,
tccd_l_wr2: read_pair(&spd, OFF_TCCD_L_WR2)?,
tfaw: read_pair(&spd, OFF_TFAW)?,
twtr_l: read_pair(&spd, OFF_TWTR_L)?,
twtr_s: read_pair(&spd, OFF_TWTR_S)?,
trtp: read_pair(&spd, OFF_TRTP)?,
})
}
fn ps_units(raw: u16) -> Picoseconds {
Picoseconds(u32::from(raw))
}
fn ns_units(raw: u16) -> Picoseconds {
Picoseconds(u32::from(raw) * 1000)
}
fn read_le_u16(spd: &SpdImage, offset: usize) -> Result<u16, DecodeError> {
Ok(u16::from_le_bytes([
spd.byte(offset)?,
spd.byte(offset + 1)?,
]))
}
fn read_pair(spd: &SpdImage, offset: usize) -> Result<TimingPair, DecodeError> {
Ok(TimingPair {
time: ps_units(read_le_u16(spd, offset)?),
clocks: ClockCycles(spd.byte(offset + 2)?),
})
}
fn decode_cas_latencies(spd: &SpdImage) -> Result<CasLatencies, DecodeError> {
let mut mask: u64 = 0;
for i in 0..CAS_LATENCY_BYTES {
mask |= u64::from(spd.byte(OFF_CAS_LATENCIES + i)?) << (8 * i);
}
Ok(CasLatencies(mask))
}
pub(crate) fn data_rate_mt_s(tckavg_min: Picoseconds) -> u32 {
if tckavg_min.0 == 0 {
return 0;
}
let raw = 2_000_000 / tckavg_min.0;
((raw + 50) / 100) * 100
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ps_units_is_identity_in_picoseconds() {
assert_eq!(ps_units(0x01A0), Picoseconds(416));
assert_eq!(ps_units(30000), Picoseconds(30000));
}
#[test]
fn ns_units_scales_nanoseconds_to_picoseconds() {
assert_eq!(ns_units(295), Picoseconds(295_000));
assert_eq!(ns_units(130), Picoseconds(130_000));
}
#[test]
fn le_u16_reads_low_byte_first() {
let spd = SpdImage::new(&[0xA0, 0x01]);
assert_eq!(read_le_u16(&spd, 0).unwrap(), 0x01A0);
}
#[test]
fn read_pair_reads_ps_then_nck() {
let spd = SpdImage::new(&[0x88, 0x13, 0x08]);
let pair = read_pair(&spd, 0).unwrap();
assert_eq!(pair.time, Picoseconds(5000));
assert_eq!(pair.clocks, ClockCycles(8));
}
#[test]
fn cas_latency_bitmask_decodes_to_even_cl_set() {
let mut img = [0u8; OFF_CAS_LATENCIES + CAS_LATENCY_BYTES];
img[OFF_CAS_LATENCIES] = 0xFE;
img[OFF_CAS_LATENCIES + 1] = 0x07;
let spd = SpdImage::new(&img);
let cl = decode_cas_latencies(&spd).unwrap();
assert!(cl.contains(22));
assert!(cl.contains(40));
assert!(!cl.contains(20)); assert!(!cl.contains(42)); assert!(!cl.contains(23)); assert_eq!(cl.iter().count(), 10);
assert_eq!(cl.iter().next(), Some(22));
assert_eq!(cl.iter().last(), Some(40));
}
#[test]
fn data_rate_rounds_to_nearest_hundred() {
assert_eq!(data_rate_mt_s(Picoseconds(416)), 4800);
assert_eq!(data_rate_mt_s(Picoseconds(0)), 0);
}
}