use std::num::NonZeroU64;
use crate::common::{LineAddr, PhysAddr};
use crate::sim::components::{ComponentId, ReqId};
use crate::sim::handle::{Handle, HandleCtx};
use crate::sim::memory::GlobalMemory;
use crate::sim::packet::{AccessSize, HitLevel, MemOp, MemRespData, MesiState, Packet};
const CACHE_LINE_BYTES: u64 = 64;
#[derive(Clone, Copy, Debug)]
pub struct DramConfig {
pub t_cas: u64,
pub t_ras: u64,
pub t_pre: u64,
pub t_rrd: u64,
pub num_banks: usize,
pub row_size_bytes: usize,
pub t_refi: u64,
pub t_rfc: u64,
}
#[derive(Debug)]
struct BankState {
open_row: Option<u64>,
busy_until: u64,
}
#[derive(Clone, Copy, Debug)]
pub struct Bandwidth {
bytes_per_second: NonZeroU64,
clock_hz: u64,
}
impl Bandwidth {
#[must_use]
pub const fn new(bytes_per_second: NonZeroU64, clock_hz: u64) -> Self {
Self { bytes_per_second, clock_hz }
}
fn occupancy(self, bytes: u64) -> u64 {
let ticks = u128::from(bytes) * u128::from(self.clock_hz);
let per_second = u128::from(self.bytes_per_second.get());
u64::try_from(ticks.div_ceil(per_second)).unwrap_or(u64::MAX).max(1)
}
}
#[derive(Debug)]
pub struct SimpleController {
latency: u64,
bandwidth: Bandwidth,
busy_until: u64,
}
impl SimpleController {
pub const fn new(latency: u64, bandwidth: Bandwidth) -> Self {
Self { latency, bandwidth, busy_until: 0 }
}
}
impl Handle for SimpleController {
fn handle(&mut self, packet: Packet, source: ComponentId, ctx: &mut HandleCtx<'_>) {
if let Packet::MemReq { req_id, paddr, size, op, .. } = packet {
if is_dataless_maintenance(&op) {
acknowledge_now(req_id, paddr, source, ctx);
return;
}
let data = service_request(paddr, size, &op, ctx.memory);
let started = ctx.cycle.max(self.busy_until);
self.busy_until = started + self.bandwidth.occupancy(size.bytes() as u64);
ctx.scheduler.schedule(
started + self.latency,
source,
ctx.self_id,
Packet::MemResp {
req_id,
line_addr: LineAddr::from_phys(paddr, CACHE_LINE_BYTES),
data,
hit_level: HitLevel::Dram,
state: MesiState::Exclusive,
},
);
}
}
}
#[derive(Debug)]
pub struct DramController {
banks: Vec<BankState>,
num_banks: usize,
t_cas: u64,
t_ras: u64,
t_pre: u64,
t_rrd: u64,
t_refi: u64,
t_rfc: u64,
row_mask: u64,
row_shift: u32,
last_activate_cycle: Option<u64>,
next_refresh_cycle: u64,
}
impl DramController {
pub fn new(cfg: DramConfig) -> Self {
debug_assert!(
cfg.row_size_bytes.is_power_of_two(),
"row_size_bytes must be a power of two"
);
debug_assert!(cfg.num_banks > 0, "num_banks must be > 0");
let row_shift = cfg.row_size_bytes.trailing_zeros();
let row_mask = !(cfg.row_size_bytes as u64 - 1);
let mut banks = Vec::with_capacity(cfg.num_banks);
for _ in 0..cfg.num_banks {
banks.push(BankState { open_row: None, busy_until: 0 });
}
Self {
banks,
num_banks: cfg.num_banks,
t_cas: cfg.t_cas,
t_ras: cfg.t_ras,
t_pre: cfg.t_pre,
t_rrd: cfg.t_rrd,
t_refi: cfg.t_refi,
t_rfc: cfg.t_rfc,
row_mask,
row_shift,
last_activate_cycle: None,
next_refresh_cycle: if cfg.t_refi > 0 { cfg.t_refi } else { u64::MAX },
}
}
#[inline]
const fn bank_index(&self, addr: u64) -> usize {
((addr >> self.row_shift) as usize) % self.num_banks
}
#[inline]
const fn row_addr(&self, addr: u64) -> u64 {
addr & self.row_mask
}
fn handle_refresh(&mut self, current_cycle: u64) -> u64 {
if self.t_refi == 0 {
return current_cycle;
}
let mut effective_cycle = current_cycle;
while effective_cycle >= self.next_refresh_cycle {
let refresh_end = self.next_refresh_cycle + self.t_rfc;
for bank in &mut self.banks {
if bank.busy_until < refresh_end {
bank.busy_until = refresh_end;
}
bank.open_row = None;
}
self.next_refresh_cycle += self.t_refi;
if effective_cycle < refresh_end {
effective_cycle = refresh_end;
}
}
effective_cycle
}
const fn activate(&mut self, mut ready_cycle: u64) -> u64 {
if let Some(last_act) = self.last_activate_cycle {
let earliest_activate = last_act + self.t_rrd;
if ready_cycle < earliest_activate {
ready_cycle = earliest_activate;
}
}
self.last_activate_cycle = Some(ready_cycle);
ready_cycle
}
fn compute_latency(&mut self, addr: u64, current_cycle: u64) -> u64 {
let mut ready_cycle = self.handle_refresh(current_cycle);
let bank_idx = self.bank_index(addr);
let row = self.row_addr(addr);
if ready_cycle < self.banks[bank_idx].busy_until {
ready_cycle = self.banks[bank_idx].busy_until;
}
match self.banks[bank_idx].open_row {
Some(open_row) if open_row == row => {
self.banks[bank_idx].busy_until = ready_cycle + self.t_cas;
(ready_cycle - current_cycle) + self.t_cas
}
Some(_) => {
ready_cycle += self.t_pre;
ready_cycle = self.activate(ready_cycle);
self.banks[bank_idx].open_row = Some(row);
self.banks[bank_idx].busy_until = ready_cycle + self.t_ras;
(ready_cycle - current_cycle) + self.t_ras + self.t_cas
}
None => {
ready_cycle = self.activate(ready_cycle);
self.banks[bank_idx].open_row = Some(row);
self.banks[bank_idx].busy_until = ready_cycle + self.t_ras;
(ready_cycle - current_cycle) + self.t_ras + self.t_cas
}
}
}
}
impl Handle for DramController {
fn handle(&mut self, packet: Packet, source: ComponentId, ctx: &mut HandleCtx<'_>) {
if let Packet::MemReq { req_id, paddr, size, op, .. } = packet {
if is_dataless_maintenance(&op) {
acknowledge_now(req_id, paddr, source, ctx);
return;
}
let latency = self.compute_latency(paddr.val(), ctx.cycle);
let data = service_request(paddr, size, &op, ctx.memory);
ctx.scheduler.schedule(
ctx.cycle + latency,
source,
ctx.self_id,
Packet::MemResp {
req_id,
line_addr: LineAddr::from_phys(paddr, CACHE_LINE_BYTES),
data,
hit_level: HitLevel::Dram,
state: MesiState::Exclusive,
},
);
}
}
}
pub trait MemoryController: Handle + Send + Sync + std::fmt::Debug {
fn tick(&mut self, _ctx: &mut HandleCtx<'_>) {}
fn quiet_until(&self, _cycle: u64) -> Option<u64> {
None
}
fn skip_quiet(&mut self, _ctx: &mut HandleCtx<'_>) {}
fn resume_at(&mut self, cycle: u64);
}
impl MemoryController for SimpleController {
fn resume_at(&mut self, _cycle: u64) {
self.busy_until = 0;
}
}
impl MemoryController for DramController {
fn resume_at(&mut self, cycle: u64) {
for bank in &mut self.banks {
*bank = BankState { open_row: None, busy_until: 0 };
}
self.last_activate_cycle = None;
self.next_refresh_cycle = if self.t_refi > 0 { cycle + self.t_refi } else { u64::MAX };
}
}
fn service_request(
paddr: PhysAddr,
size: AccessSize,
op: &MemOp,
memory: &mut GlobalMemory,
) -> MemRespData {
if op.takes_effect_when_served(size) {
return memory.perform(paddr, size, op);
}
match op {
MemOp::Read | MemOp::ReadOwn | MemOp::Fetch | MemOp::Atomic { .. } => {
read_response(memory, paddr, size)
}
MemOp::Write { .. }
| MemOp::Writeback { .. }
| MemOp::Maintain { .. }
| MemOp::Prefetch { .. } => MemRespData::Small(0),
}
}
fn acknowledge_now(req_id: ReqId, paddr: PhysAddr, source: ComponentId, ctx: &mut HandleCtx<'_>) {
ctx.scheduler.schedule(
ctx.cycle,
source,
ctx.self_id,
Packet::MemResp {
req_id,
line_addr: LineAddr::from_phys(paddr, CACHE_LINE_BYTES),
data: MemRespData::Small(0),
hit_level: HitLevel::Dram,
state: MesiState::Exclusive,
},
);
}
const fn is_dataless_maintenance(op: &MemOp) -> bool {
matches!(op, MemOp::Maintain { dirty: false, .. })
}
pub fn read_response(memory: &GlobalMemory, paddr: PhysAddr, size: AccessSize) -> MemRespData {
if size == AccessSize::Line {
let line = memory
.read_bytes(paddr, CACHE_LINE_BYTES as usize)
.unwrap_or_else(|| vec![0; CACHE_LINE_BYTES as usize].into_boxed_slice());
return MemRespData::Line(line);
}
MemRespData::Small(memory.read(paddr, size.bytes()).unwrap_or(0))
}