pub mod timing;
use std::collections::BTreeMap;
use serde::Deserialize;
use crate::config::AddressMappingKind;
pub use timing::{Constraint, Ddr5SpeedBin, Ddr5Timing, Ddr5TimingField};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Ddr5Config {
pub channels: u8,
pub subchannels_per_channel: u8,
pub ranks_per_channel: u8,
pub bank_groups_per_rank: u8,
pub banks_per_group: u8,
pub row_bits: u8,
pub column_bits: u8,
pub row_size_bytes: u32,
pub read_queue_entries: usize,
pub write_queue_entries: usize,
pub write_high_watermark: usize,
pub write_low_watermark: usize,
pub min_writes_per_switch: usize,
pub frontend_latency: u64,
pub backend_latency: u64,
pub scheduler: SchedulerKind,
pub refresh: RefreshKind,
pub power_down: PowerDownPolicy,
pub ecc: EccKind,
pub address_mapping: AddressMappingKind,
pub timing: Ddr5Timing,
}
impl Ddr5Config {
#[must_use]
pub const fn ddr5_4800() -> Self {
let bin = Ddr5SpeedBin::DDR5_4800B;
Self {
channels: 2,
subchannels_per_channel: 2,
ranks_per_channel: 2,
bank_groups_per_rank: 8,
banks_per_group: 4,
row_bits: 16,
column_bits: 6,
row_size_bytes: 4096,
read_queue_entries: 64,
write_queue_entries: 64,
write_high_watermark: 54,
write_low_watermark: 32,
min_writes_per_switch: 16,
frontend_latency: Constraint::ps(10_000).cycles(bin.data_rate_mts),
backend_latency: Constraint::ps(10_000).cycles(bin.data_rate_mts),
scheduler: SchedulerKind::FrFcfs,
refresh: RefreshKind::AllBank,
power_down: PowerDownPolicy::Disabled,
ecc: EccKind::None,
address_mapping: AddressMappingKind::RoRaBaChCo,
timing: Ddr5Timing::from_bin(&bin),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum PowerDownPolicy {
#[default]
Disabled,
AfterIdle {
idle_clocks: u64,
},
}
impl Default for Ddr5Config {
fn default() -> Self {
Self::ddr5_4800()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Deserialize)]
pub enum Ddr5SpeedBinName {
#[default]
#[serde(alias = "4800B", alias = "ddr5_4800b", alias = "DDR5-4800B")]
Ddr5_4800B,
#[serde(alias = "5600B", alias = "ddr5_5600b", alias = "DDR5-5600B")]
Ddr5_5600B,
}
impl Ddr5SpeedBinName {
const fn bin(self) -> Ddr5SpeedBin {
match self {
Self::Ddr5_4800B => Ddr5SpeedBin::DDR5_4800B,
Self::Ddr5_5600B => Ddr5SpeedBin::DDR5_5600B,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Deserialize)]
pub enum EccMode {
#[default]
None,
SecDed,
ChipKill,
}
#[derive(Clone, Debug, PartialEq, Eq, Default, Deserialize)]
#[serde(try_from = "Ddr5ParamsRaw")]
pub struct Ddr5Params {
raw: Ddr5ParamsRaw,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Ddr5ParamsError {
NotPowerOfTwo {
field: &'static str,
value: u8,
},
Zero {
field: &'static str,
},
WriteQueue,
TooManyBanks,
}
impl std::fmt::Display for Ddr5ParamsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotPowerOfTwo { field, value } => {
write!(f, "ddr5.{field} must be a power of two, got {value}")
}
Self::Zero { field } => write!(f, "ddr5.{field} must be non-zero"),
Self::WriteQueue => {
write!(f, "ddr5 write watermarks must satisfy low < high <= write_queue_entries")
}
Self::TooManyBanks => write!(f, "ddr5 supports at most 64 banks per rank"),
}
}
}
impl std::error::Error for Ddr5ParamsError {}
impl TryFrom<Ddr5ParamsRaw> for Ddr5Params {
type Error = Ddr5ParamsError;
fn try_from(raw: Ddr5ParamsRaw) -> Result<Self, Self::Error> {
for (field, value) in [
("channels", raw.channels),
("subchannels_per_channel", raw.subchannels_per_channel),
("ranks_per_channel", raw.ranks_per_channel),
("bank_groups_per_rank", raw.bank_groups_per_rank),
("banks_per_group", raw.banks_per_group),
] {
if value == 0 {
return Err(Ddr5ParamsError::Zero { field });
}
if !value.is_power_of_two() {
return Err(Ddr5ParamsError::NotPowerOfTwo { field, value });
}
}
if u32::from(raw.bank_groups_per_rank) * u32::from(raw.banks_per_group) > 64 {
return Err(Ddr5ParamsError::TooManyBanks);
}
if raw.write_queue_entries == 0
|| raw.write_high_watermark > raw.write_queue_entries
|| raw.write_low_watermark >= raw.write_high_watermark
{
return Err(Ddr5ParamsError::WriteQueue);
}
if raw.read_queue_entries == 0 {
return Err(Ddr5ParamsError::Zero { field: "read_queue_entries" });
}
Ok(Self { raw })
}
}
impl Ddr5Params {
#[must_use]
pub fn to_config(&self) -> Ddr5Config {
let raw = &self.raw;
let bin = raw.speed_bin.bin();
let mut timing = Ddr5Timing::from_bin(&bin);
for (&field, &clocks) in &raw.timing {
timing.set(field, clocks);
}
let ns_to_clocks = |ns: u64| Constraint::ps(ns * 1000).cycles(bin.data_rate_mts);
let power_down = raw.power_down_idle_ns.map_or(PowerDownPolicy::Disabled, |ns| {
PowerDownPolicy::AfterIdle { idle_clocks: ns_to_clocks(ns) }
});
let ecc = match raw.ecc {
EccMode::None => EccKind::None,
EccMode::SecDed => EccKind::SecDed { patrol_scrub_ns: raw.patrol_scrub_ns },
EccMode::ChipKill => EccKind::ChipKill { patrol_scrub_ns: raw.patrol_scrub_ns },
};
Ddr5Config {
channels: raw.channels,
subchannels_per_channel: raw.subchannels_per_channel,
ranks_per_channel: raw.ranks_per_channel,
bank_groups_per_rank: raw.bank_groups_per_rank,
banks_per_group: raw.banks_per_group,
row_bits: raw.row_bits,
column_bits: raw.column_bits,
row_size_bytes: 1u32 << (u32::from(raw.column_bits) + 6),
read_queue_entries: raw.read_queue_entries,
write_queue_entries: raw.write_queue_entries,
write_high_watermark: raw.write_high_watermark,
write_low_watermark: raw.write_low_watermark,
min_writes_per_switch: raw.min_writes_per_switch,
frontend_latency: ns_to_clocks(raw.frontend_latency_ns),
backend_latency: ns_to_clocks(raw.backend_latency_ns),
scheduler: raw.scheduler,
refresh: raw.refresh,
power_down,
ecc,
address_mapping: raw.address_mapping,
timing,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(default)]
pub struct Ddr5ParamsRaw {
pub speed_bin: Ddr5SpeedBinName,
pub channels: u8,
pub subchannels_per_channel: u8,
pub ranks_per_channel: u8,
pub bank_groups_per_rank: u8,
pub banks_per_group: u8,
pub row_bits: u8,
pub column_bits: u8,
pub read_queue_entries: usize,
pub write_queue_entries: usize,
pub write_high_watermark: usize,
pub write_low_watermark: usize,
pub min_writes_per_switch: usize,
pub frontend_latency_ns: u64,
pub backend_latency_ns: u64,
pub scheduler: SchedulerKind,
pub refresh: RefreshKind,
pub address_mapping: AddressMappingKind,
pub power_down_idle_ns: Option<u64>,
pub ecc: EccMode,
pub patrol_scrub_ns: Option<u64>,
pub timing: BTreeMap<Ddr5TimingField, u64>,
}
impl Default for Ddr5ParamsRaw {
fn default() -> Self {
let base = Ddr5Config::ddr5_4800();
Self {
speed_bin: Ddr5SpeedBinName::Ddr5_4800B,
channels: base.channels,
subchannels_per_channel: base.subchannels_per_channel,
ranks_per_channel: base.ranks_per_channel,
bank_groups_per_rank: base.bank_groups_per_rank,
banks_per_group: base.banks_per_group,
row_bits: base.row_bits,
column_bits: base.column_bits,
read_queue_entries: base.read_queue_entries,
write_queue_entries: base.write_queue_entries,
write_high_watermark: base.write_high_watermark,
write_low_watermark: base.write_low_watermark,
min_writes_per_switch: base.min_writes_per_switch,
frontend_latency_ns: 10,
backend_latency_ns: 10,
scheduler: base.scheduler,
refresh: base.refresh,
address_mapping: base.address_mapping,
power_down_idle_ns: None,
ecc: EccMode::None,
patrol_scrub_ns: None,
timing: BTreeMap::new(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum EccKind {
#[default]
None,
SecDed {
patrol_scrub_ns: Option<u64>,
},
ChipKill {
patrol_scrub_ns: Option<u64>,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, serde::Deserialize)]
pub enum RefreshKind {
#[default]
AllBank,
SameBank,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, serde::Deserialize)]
pub enum SchedulerKind {
Fcfs,
#[default]
FrFcfs,
}