pub mod mshr;
pub mod policies;
pub mod prefetch;
pub mod stats;
pub mod writeback_buffer;
mod audit;
mod coherence;
mod inclusion;
mod request;
mod writeback;
use std::collections::{BTreeSet, VecDeque};
use self::mshr::MshrTable;
use self::policies::{
FifoPolicy, LruPolicy, MruPolicy, PlruPolicy, RandomPolicy, ReplacementPolicy,
};
use self::stats::CacheStatPaths;
use self::writeback_buffer::WritebackBuffer;
use crate::common::{CoreId, LineAddr, PhysAddr, VirtAddr};
use crate::config::{CacheConfig, InclusionPolicy, PrefetcherKind, ReplacementPolicyKind};
use crate::sim::components::{CacheId, ComponentId, ReqId};
use crate::sim::handle::{Handle, HandleCtx};
use crate::sim::packet::{
AccessSize, CacheLevel, HitLevel, MemOp, MemRespData, MesiState, Packet, ProbeKind,
};
use crate::sim::stats::Stats;
use crate::soc::cache::prefetch::{
NextLinePrefetcher, Prefetcher, StoreStreamPrefetcher, StreamPrefetcher, StridePrefetcher,
TaggedPrefetcher,
};
#[derive(Clone, Copy, Debug, Default)]
struct CacheLine {
tag: u64,
state: MesiState,
upper: u8,
prefetched: bool,
}
impl CacheLine {
const fn valid(self) -> bool {
!matches!(self.state, MesiState::Invalid)
}
const fn dirty(self) -> bool {
matches!(self.state, MesiState::Modified)
}
}
#[derive(Clone, Debug)]
struct WaitingFill {
req_id: ReqId,
line_addr: LineAddr,
data: MemRespData,
hit_level: HitLevel,
granted: MesiState,
}
#[derive(Clone, Debug)]
struct BlockedRequest {
source: ComponentId,
req_id: ReqId,
paddr: PhysAddr,
vaddr: Option<VirtAddr>,
pc: Option<VirtAddr>,
size: AccessSize,
op: MemOp,
}
#[derive(Clone, Copy, Debug)]
struct Forwarded {
ours: ReqId,
source: ComponentId,
theirs: ReqId,
line: LineAddr,
}
#[derive(Clone, Copy, Debug)]
enum ProbeOrigin {
Probe {
from: ComponentId,
txn: ReqId,
},
Snoop {
txn: ReqId,
},
}
#[derive(Clone, Copy, Debug)]
struct PendingProbe {
ours: ReqId,
origin: ProbeOrigin,
line: LineAddr,
kind: ProbeKind,
remaining: usize,
dirty: bool,
had_copy: bool,
}
pub struct Cache {
pub id: CacheId,
pub level: CacheLevel,
pub upstream: Vec<ComponentId>,
pub downstream: Option<ComponentId>,
pub latency: u64,
response_latency: u64,
pub enabled: bool,
pub prefetcher: Option<Box<dyn Prefetcher + Send + Sync>>,
store_prefetcher: Option<StoreStreamPrefetcher>,
pub stat_paths: CacheStatPaths,
upstream_inclusion: InclusionPolicy,
clean_victims_to_downstream: bool,
coherent: Option<CoreId>,
lines: Vec<CacheLine>,
num_sets: usize,
ways: usize,
line_bytes: usize,
policy: Box<dyn ReplacementPolicy + Send + Sync>,
mshrs: MshrTable,
targets_per_mshr: usize,
full_mshr: Option<ReqId>,
writebacks: WritebackBuffer,
blocked: VecDeque<BlockedRequest>,
forwarded: Vec<Forwarded>,
after_fill: Vec<BlockedRequest>,
pending_probes: Vec<PendingProbe>,
waiting_fills: VecDeque<WaitingFill>,
handed_up: BTreeSet<LineAddr>,
next_req: u64,
}
impl std::fmt::Debug for Cache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Cache")
.field("id", &self.id)
.field("level", &self.level)
.field("latency", &self.latency)
.field("enabled", &self.enabled)
.field("num_sets", &self.num_sets)
.field("ways", &self.ways)
.field("line_bytes", &self.line_bytes)
.field("mshrs", &self.mshrs)
.field("writebacks", &self.writebacks)
.finish_non_exhaustive()
}
}
impl Cache {
pub fn new(id: CacheId, level: CacheLevel, config: &CacheConfig, stat_subject: &str) -> Self {
let safe_ways = if config.ways == 0 { 1 } else { config.ways };
let safe_line = if config.line_bytes == 0 { 64 } else { config.line_bytes };
let safe_size = if config.size_bytes == 0 { 4096 } else { config.size_bytes };
let num_lines = safe_size / safe_line;
let num_sets = (num_lines / safe_ways).max(1);
let policy: Box<dyn ReplacementPolicy + Send + Sync> = match config.policy {
ReplacementPolicyKind::Fifo => Box::new(FifoPolicy::new(num_sets, safe_ways)),
ReplacementPolicyKind::Random => Box::new(RandomPolicy::new(num_sets, safe_ways)),
ReplacementPolicyKind::Plru => Box::new(PlruPolicy::new(num_sets, safe_ways)),
ReplacementPolicyKind::Lru => Box::new(LruPolicy::new(num_sets, safe_ways)),
ReplacementPolicyKind::Mru => Box::new(MruPolicy::new(num_sets, safe_ways)),
};
let prefetcher: Option<Box<dyn Prefetcher + Send + Sync>> = match config.prefetcher {
PrefetcherKind::NextLine => {
Some(Box::new(NextLinePrefetcher::new(safe_line, config.prefetch_degree)))
}
PrefetcherKind::Stride => Some(Box::new(StridePrefetcher::new(
safe_line,
config.prefetch_table_size,
config.prefetch_degree,
))),
PrefetcherKind::Stream => {
Some(Box::new(StreamPrefetcher::new(safe_line, config.prefetch_degree)))
}
PrefetcherKind::Tagged => {
Some(Box::new(TaggedPrefetcher::new(safe_line, config.prefetch_degree)))
}
PrefetcherKind::None => None,
};
Self {
id,
level,
upstream: Vec::new(),
downstream: None,
latency: config.latency,
response_latency: config.response_latency,
enabled: config.enabled,
prefetcher,
store_prefetcher: None,
stat_paths: CacheStatPaths::new(stat_subject),
upstream_inclusion: InclusionPolicy::Nine,
clean_victims_to_downstream: false,
coherent: None,
lines: vec![CacheLine::default(); num_sets * safe_ways],
num_sets,
ways: safe_ways,
line_bytes: safe_line,
policy,
mshrs: MshrTable::new(config.mshr_count),
targets_per_mshr: config.targets_per_mshr.max(1),
full_mshr: None,
writebacks: WritebackBuffer::new(config.write_buffers),
blocked: VecDeque::new(),
forwarded: Vec::new(),
after_fill: Vec::new(),
pending_probes: Vec::new(),
handed_up: BTreeSet::new(),
waiting_fills: VecDeque::new(),
next_req: 0,
}
}
pub const fn set_downstream(&mut self, downstream: ComponentId) {
self.downstream = Some(downstream);
}
pub fn add_upstream(&mut self, upstream: ComponentId) {
self.upstream.push(upstream);
}
pub const fn set_upstream_inclusion(&mut self, policy: InclusionPolicy) {
self.upstream_inclusion = policy;
}
pub const fn set_clean_victims_to_downstream(&mut self, enabled: bool) {
self.clean_victims_to_downstream = enabled;
}
pub fn set_store_prefetcher(&mut self, prefetcher: StoreStreamPrefetcher) {
self.store_prefetcher = Some(prefetcher);
}
pub const fn set_coherent(&mut self, core: CoreId) {
self.coherent = Some(core);
}
#[must_use]
pub fn lines_in_flight(&self) -> Vec<LineAddr> {
self.mshrs
.iter()
.map(|m| m.line)
.chain(self.writebacks.lines())
.chain(self.pending_probes.iter().map(|p| p.line))
.chain(self.forwarded.iter().map(|f| f.line))
.collect()
}
#[must_use]
pub const fn is_enabled(&self) -> bool {
self.enabled
}
#[inline]
pub const fn line_bytes(&self) -> usize {
self.line_bytes
}
#[must_use]
pub const fn line_count(&self) -> usize {
self.lines.len()
}
#[cfg(test)]
#[must_use]
pub const fn writebacks(&self) -> &WritebackBuffer {
&self.writebacks
}
#[cfg(test)]
#[must_use]
pub fn blocked_requests(&self) -> usize {
self.blocked.len()
}
#[must_use]
pub fn held_lines(&self) -> Vec<(LineAddr, MesiState)> {
let mut held = Vec::new();
for (index, line) in self.lines.iter().enumerate() {
if line.valid() {
let set_index = index / self.ways;
held.push((self.line_of(self.reconstruct_addr(set_index, line.tag)), line.state));
}
}
held
}
#[must_use]
pub fn duplicate_lines(&self) -> Vec<LineAddr> {
let mut duplicates = Vec::new();
for set_index in 0..self.num_sets {
let set = &self.lines[set_index * self.ways..(set_index + 1) * self.ways];
for (i, line) in set.iter().enumerate() {
if line.valid()
&& set[..i].iter().any(|other| other.valid() && other.tag == line.tag)
{
duplicates.push(self.line_of(self.reconstruct_addr(set_index, line.tag)));
}
}
}
duplicates
}
const fn line_of(&self, addr: u64) -> LineAddr {
LineAddr::from_phys(PhysAddr::new(addr), self.line_bytes as u64)
}
const fn set_index(&self, addr: u64) -> usize {
((addr as usize) / self.line_bytes) % self.num_sets
}
const fn tag_of(&self, addr: u64) -> u64 {
addr / (self.line_bytes * self.num_sets) as u64
}
const fn reconstruct_addr(&self, set_index: usize, tag: u64) -> u64 {
tag * (self.line_bytes * self.num_sets) as u64 + (set_index * self.line_bytes) as u64
}
fn find_way(&self, addr: u64) -> Option<usize> {
let set_index = self.set_index(addr);
let tag = self.tag_of(addr);
(0..self.ways).find(|&way| {
let line = self.lines[set_index * self.ways + way];
line.valid() && line.tag == tag
})
}
pub fn contains(&self, addr: u64) -> bool {
self.enabled && self.find_way(addr).is_some()
}
fn clean_line(&mut self, addr: u64) -> bool {
let Some(way) = self.find_way(addr) else { return false };
let index = self.set_index(addr) * self.ways + way;
let was_dirty = self.lines[index].dirty();
if was_dirty {
self.lines[index].state = MesiState::Exclusive;
}
was_dirty
}
fn invalidate_line(&mut self, addr: u64, stats: &mut Stats) -> bool {
let Some(way) = self.find_way(addr) else { return false };
let index = self.set_index(addr) * self.ways + way;
let was_dirty = self.lines[index].dirty();
self.drop_line(index, stats);
was_dirty
}
pub fn invalidate_all(&mut self, stats: &mut Stats) {
for index in 0..self.lines.len() {
self.drop_line(index, stats);
}
}
fn drop_line(&mut self, index: usize, stats: &mut Stats) {
let line = &mut self.lines[index];
if line.valid() && line.prefetched {
stats.counter(self.stat_paths.prefetches_unused).inc();
}
line.state = MesiState::Invalid;
line.prefetched = false;
}
fn note_request_for(&mut self, index: usize, stats: &mut Stats) {
let line = &mut self.lines[index];
if line.prefetched {
stats.counter(self.stat_paths.prefetches_useful).inc();
line.prefetched = false;
}
}
}
impl Handle for Cache {
fn handle(&mut self, packet: Packet, source: ComponentId, ctx: &mut HandleCtx<'_>) {
match packet {
Packet::MemReq { req_id, paddr, vaddr, pc, size, op } => {
self.on_request(BlockedRequest { source, req_id, paddr, vaddr, pc, size, op }, ctx);
}
Packet::MemResp { req_id, line_addr, data, hit_level, state } => {
self.on_response(req_id, line_addr, data, hit_level, state, ctx);
}
Packet::Probe { line_addr, kind, txn } => {
self.on_probe(line_addr, kind, txn, source, ctx);
}
Packet::ProbeResp { txn, had_copy, dirty, .. } => {
self.on_probe_resp(txn, had_copy, dirty, source, ctx);
}
Packet::Coh(msg) => self.on_coherence(msg, ctx),
Packet::CacheInval { line_addr } => self.on_back_invalidate(line_addr, ctx),
Packet::DramCmd { .. } => {}
}
}
}