use crate::sim::stats::StatId;
use crate::sim::stats::{Formula, Meta, Stats};
use crate::soc::memory::ddr5::state::{Bank, Subchannel};
#[derive(Clone, Debug, Default)]
pub struct SubchannelCounters {
pub reads: u64,
pub writes: u64,
pub writes_merged: u64,
pub reads_hit_write_queue: u64,
pub scrub_reads: u64,
pub activates: u64,
pub precharges: u64,
pub precharge_alls: u64,
pub refreshes: u64,
pub row_hits: u64,
pub row_misses: u64,
pub power_down_entries: u64,
pub power_down_exits: u64,
pub bus_busy_clocks: u64,
pub clocks: u64,
pub read_admission_stalls: u64,
pub write_admission_stalls: u64,
pub read_latency_samples: Vec<u64>,
pub read_queue_depth_samples: Vec<u64>,
pub write_queue_depth_samples: Vec<u64>,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct BankCounters {
pub activates: u64,
pub reads: u64,
pub writes: u64,
pub row_hits: u64,
pub row_misses: u64,
}
impl BankCounters {
#[must_use]
pub const fn new() -> Self {
Self { activates: 0, reads: 0, writes: 0, row_hits: 0, row_misses: 0 }
}
}
#[derive(Debug)]
pub struct ControllerStatPaths {
subchannels: Vec<SubchannelPaths>,
subchannels_per_channel: usize,
banks_per_rank: usize,
}
#[derive(Debug)]
struct SubchannelPaths {
reads: StatId,
writes: StatId,
writes_merged: StatId,
reads_hit_write_queue: StatId,
scrub_reads: StatId,
activates: StatId,
precharges: StatId,
precharge_alls: StatId,
refreshes: StatId,
row_hits: StatId,
row_misses: StatId,
row_hit_rate: StatId,
power_down_entries: StatId,
power_down_exits: StatId,
bus_busy_clocks: StatId,
clocks: StatId,
data_bus_utilization: StatId,
read_admission_stalls: StatId,
write_admission_stalls: StatId,
read_latency: StatId,
read_queue_depth: StatId,
write_queue_depth: StatId,
banks: Vec<BankPaths>,
}
#[derive(Debug)]
struct BankPaths {
activates: StatId,
reads: StatId,
writes: StatId,
row_hits: StatId,
row_misses: StatId,
}
impl ControllerStatPaths {
#[must_use]
pub fn new(
index: u32,
channels: usize,
subchannels_per_channel: usize,
ranks: usize,
banks_per_rank: usize,
) -> Self {
let mut subchannels = Vec::with_capacity(channels * subchannels_per_channel);
for ch in 0..channels {
for sc in 0..subchannels_per_channel {
let prefix = format!("memctrl{index}.ch{ch}.sc{sc}");
let mut banks = Vec::with_capacity(ranks * banks_per_rank);
for rank in 0..ranks {
for bank in 0..banks_per_rank {
let bank_prefix = format!("{prefix}.rank{rank}.bank{bank}");
banks.push(BankPaths {
activates: StatId::of(&format!("{bank_prefix}.activates")),
reads: StatId::of(&format!("{bank_prefix}.reads")),
writes: StatId::of(&format!("{bank_prefix}.writes")),
row_hits: StatId::of(&format!("{bank_prefix}.row_hits")),
row_misses: StatId::of(&format!("{bank_prefix}.row_misses")),
});
}
}
subchannels.push(SubchannelPaths {
reads: StatId::of(&format!("{prefix}.reads")),
writes: StatId::of(&format!("{prefix}.writes")),
writes_merged: StatId::of(&format!("{prefix}.writes_merged")),
reads_hit_write_queue: StatId::of(&format!("{prefix}.reads_hit_write_queue")),
scrub_reads: StatId::of(&format!("{prefix}.scrub_reads")),
activates: StatId::of(&format!("{prefix}.activates")),
precharges: StatId::of(&format!("{prefix}.precharges")),
precharge_alls: StatId::of(&format!("{prefix}.precharge_alls")),
refreshes: StatId::of(&format!("{prefix}.refreshes")),
row_hits: StatId::of(&format!("{prefix}.row_hits")),
row_misses: StatId::of(&format!("{prefix}.row_misses")),
row_hit_rate: StatId::of(&format!("{prefix}.row_hit_rate")),
power_down_entries: StatId::of(&format!("{prefix}.power_down_entries")),
power_down_exits: StatId::of(&format!("{prefix}.power_down_exits")),
bus_busy_clocks: StatId::of(&format!("{prefix}.bus_busy_clocks")),
clocks: StatId::of(&format!("{prefix}.clocks")),
data_bus_utilization: StatId::of(&format!("{prefix}.data_bus_utilization")),
read_admission_stalls: StatId::of(&format!("{prefix}.read_admission_stalls")),
write_admission_stalls: StatId::of(&format!("{prefix}.write_admission_stalls")),
read_latency: StatId::of(&format!("{prefix}.read_latency")),
read_queue_depth: StatId::of(&format!("{prefix}.read_queue_depth")),
write_queue_depth: StatId::of(&format!("{prefix}.write_queue_depth")),
banks,
});
}
}
Self { subchannels, subchannels_per_channel, banks_per_rank }
}
pub fn register(&self, stats: &mut Stats) {
for sc in &self.subchannels {
stats.register(sc.reads, Meta::events("demand reads admitted"));
stats.register(sc.writes, Meta::events("writes admitted"));
stats.register(sc.writes_merged, Meta::events("writes merged into a queued write"));
stats.register(
sc.reads_hit_write_queue,
Meta::events("reads served from the write queue"),
);
stats.register(sc.scrub_reads, Meta::events("patrol-scrub reads"));
stats.register(sc.activates, Meta::events("ACTIVATE commands"));
stats.register(sc.precharges, Meta::events("PRECHARGE commands"));
stats.register(sc.precharge_alls, Meta::events("PRECHARGE-ALL commands"));
stats.register(sc.refreshes, Meta::events("REFRESH commands"));
stats.register(sc.row_hits, Meta::events("column commands hitting an open row"));
stats.register(sc.row_misses, Meta::events("column commands needing an ACTIVATE"));
stats.register(sc.power_down_entries, Meta::events("power-down entries"));
stats.register(sc.power_down_exits, Meta::events("power-down exits"));
stats.register(
sc.bus_busy_clocks,
Meta::cycles("DRAM clocks with a burst on the data bus"),
);
stats.register(sc.clocks, Meta::cycles("DRAM clocks elapsed"));
stats.register(
sc.read_admission_stalls,
Meta::cycles("clocks a read waited for a queue slot"),
);
stats.register(
sc.write_admission_stalls,
Meta::cycles("clocks a write waited for a queue slot"),
);
stats.register_histogram(
sc.read_latency,
Meta::cycles("DRAM clocks from a demand read's arrival to its last data"),
);
stats.register_histogram(
sc.read_queue_depth,
Meta::events("reads already queued when a read was admitted"),
);
stats.register_histogram(
sc.write_queue_depth,
Meta::events("writes already queued when a write was admitted"),
);
stats.derive(
sc.row_hit_rate,
Formula::Ratio { numerator: sc.row_hits, other: sc.row_misses },
Meta::ratio("row-buffer hit rate"),
);
stats.derive(
sc.data_bus_utilization,
Formula::Div(sc.bus_busy_clocks, sc.clocks),
Meta::ratio("fraction of DRAM clocks the data bus was busy"),
);
}
}
pub fn publish(
&self,
stats: &mut Stats,
channel: usize,
subchannel_index: usize,
subchannel: &mut Subchannel,
) {
let paths = &self.subchannels[channel * self.subchannels_per_channel + subchannel_index];
let c = &mut subchannel.counters;
stats.counter(paths.reads).add(c.reads);
stats.counter(paths.writes).add(c.writes);
stats.counter(paths.writes_merged).add(c.writes_merged);
stats.counter(paths.reads_hit_write_queue).add(c.reads_hit_write_queue);
stats.counter(paths.scrub_reads).add(c.scrub_reads);
stats.counter(paths.activates).add(c.activates);
stats.counter(paths.precharges).add(c.precharges);
stats.counter(paths.precharge_alls).add(c.precharge_alls);
stats.counter(paths.refreshes).add(c.refreshes);
stats.counter(paths.row_hits).add(c.row_hits);
stats.counter(paths.row_misses).add(c.row_misses);
stats.counter(paths.power_down_entries).add(c.power_down_entries);
stats.counter(paths.power_down_exits).add(c.power_down_exits);
stats.counter(paths.bus_busy_clocks).add(c.bus_busy_clocks);
stats.counter(paths.clocks).add(c.clocks);
stats.counter(paths.read_admission_stalls).add(c.read_admission_stalls);
stats.counter(paths.write_admission_stalls).add(c.write_admission_stalls);
for sample in c.read_latency_samples.drain(..) {
stats.histogram(paths.read_latency).record(sample);
}
for sample in c.read_queue_depth_samples.drain(..) {
stats.histogram(paths.read_queue_depth).record(sample);
}
for sample in c.write_queue_depth_samples.drain(..) {
stats.histogram(paths.write_queue_depth).record(sample);
}
let had_bank_activity = c.activates + c.row_hits + c.row_misses > 0;
*c = SubchannelCounters::default();
if !had_bank_activity {
return;
}
for (rank_index, rank) in subchannel.ranks.iter_mut().enumerate() {
for (bank_index, bank) in rank.banks.iter_mut().enumerate() {
let bank_paths = &paths.banks[rank_index * self.banks_per_rank + bank_index];
publish_bank(stats, bank_paths, bank);
}
}
}
}
fn publish_bank(stats: &mut Stats, paths: &BankPaths, bank: &mut Bank) {
let c = bank.counters;
if c.activates + c.reads + c.writes == 0 {
return;
}
stats.counter(paths.activates).add(c.activates);
stats.counter(paths.reads).add(c.reads);
stats.counter(paths.writes).add(c.writes);
stats.counter(paths.row_hits).add(c.row_hits);
stats.counter(paths.row_misses).add(c.row_misses);
bank.counters = BankCounters::default();
}