use crate::config::ddr5::PowerDownPolicy;
use crate::sim::components::{ChannelId, RankId, SubchannelId};
use crate::sim::packet::DramCmdKind;
use crate::soc::memory::ddr5::state::{BankState, PowerState, Rank, RefreshPhase, Subchannel};
use super::Ddr5Controller;
use super::{EmittedCommand, POWER_CMD_CYCLES, index_to_u8};
impl Ddr5Controller {
pub(super) fn power_down_entry(
&self,
subchannel: &Subchannel,
rank: &Rank,
now: u64,
) -> Option<u64> {
let PowerDownPolicy::AfterIdle { idle_clocks } = self.config.power_down else {
return None;
};
if rank.power != PowerState::Active {
return None;
}
let entry = (rank.command_floor() + idle_clocks).max(subchannel.last_data_end).max(now);
let blocked_by_refresh =
self.refresh_interval > 0 && rank.next_refresh <= entry + self.config.timing.t_pd;
(!blocked_by_refresh).then_some(entry)
}
pub(super) fn advance_power(&mut self, chan: ChannelId, subch: SubchannelId, now: u64) -> bool {
let PowerDownPolicy::AfterIdle { idle_clocks } = self.config.power_down else {
return false;
};
let t = self.config.timing;
let rank_count = self.channels[chan.as_index()].subchannels[subch.as_index()].ranks.len();
for rank_idx in 0..rank_count {
let rank = RankId::new(index_to_u8(rank_idx));
let has_work = self.rank_has_work(chan, subch, rank, now);
let sc = &mut self.channels[chan.as_index()].subchannels[subch.as_index()];
let data_bus_idle = sc.last_data_end <= now;
let rank_mut = &mut sc.ranks[rank_idx];
match rank_mut.power {
PowerState::PowerDown { since, .. } => {
if !has_work || now < since + t.t_pd || rank_mut.command_floor() > now {
continue;
}
rank_mut.power = PowerState::Active;
rank_mut.power_up_at = now + t.t_xp;
rank_mut.last_command_cycle =
rank_mut.last_command_cycle.max(now + POWER_CMD_CYCLES);
sc.last_command_cycle = sc.last_command_cycle.max(now + POWER_CMD_CYCLES);
sc.counters.power_down_exits += 1;
self.pending_commands.push(EmittedCommand {
channel: chan,
rank,
bank: 0,
row: 0,
kind: DramCmdKind::PowerDownExit,
fire_at: now,
});
return true;
}
PowerState::Active => {
let idle_since = rank_mut.command_floor();
let refreshing = rank_mut.refresh_phase != RefreshPhase::Idle
|| rank_mut.banks.iter().any(|b| b.state == BankState::Refreshing);
if has_work
|| refreshing
|| !data_bus_idle
|| now < idle_since + idle_clocks
|| rank_mut.next_refresh <= now + t.t_pd
{
continue;
}
rank_mut.power = PowerState::PowerDown {
since: now,
with_open_rows: rank_mut.has_open_row(),
};
rank_mut.last_command_cycle =
rank_mut.last_command_cycle.max(now + POWER_CMD_CYCLES);
sc.last_command_cycle = sc.last_command_cycle.max(now + POWER_CMD_CYCLES);
sc.counters.power_down_entries += 1;
self.pending_commands.push(EmittedCommand {
channel: chan,
rank,
bank: 0,
row: 0,
kind: DramCmdKind::PowerDownEntry,
fire_at: now,
});
return true;
}
}
}
false
}
pub(super) fn rank_has_work(
&self,
chan: ChannelId,
subch: SubchannelId,
rank: RankId,
now: u64,
) -> bool {
let sc = &self.channels[chan.as_index()].subchannels[subch.as_index()];
let rank_ref = &sc.ranks[rank.as_index()];
rank_ref.next_refresh <= now
|| rank_ref.refresh_phase != RefreshPhase::Idle
|| sc.read_queue.iter().chain(sc.write_queue.iter()).any(|r| r.loc.rank == rank)
}
}