use crate::common::{LineAddr, PhysAddr, VirtAddr};
use crate::sim::components::{ComponentId, ReqId};
use crate::sim::packet::{AccessSize, MemOp};
#[derive(Clone, Debug)]
pub struct MshrTarget {
pub source: ComponentId,
pub req_id: ReqId,
pub paddr: PhysAddr,
pub vaddr: Option<VirtAddr>,
pub pc: Option<VirtAddr>,
pub size: AccessSize,
pub op: MemOp,
}
#[derive(Clone, Debug)]
pub struct Mshr {
pub line: LineAddr,
pub req_id: ReqId,
pub targets: Vec<MshrTarget>,
pub deferred: Vec<MshrTarget>,
pub write: bool,
pub prefetch: bool,
pub issued_at: u64,
pub upgrade: bool,
}
impl Mshr {
#[must_use]
pub const fn target_count(&self) -> usize {
self.targets.len() + self.deferred.len()
}
}
#[derive(Debug)]
pub struct MshrTable {
entries: Vec<Mshr>,
capacity: usize,
}
impl MshrTable {
#[must_use]
pub fn new(capacity: usize) -> Self {
let capacity = capacity.max(1);
Self { entries: Vec::with_capacity(capacity), capacity }
}
#[must_use]
pub const fn capacity(&self) -> usize {
self.capacity
}
#[must_use]
pub const fn is_full(&self) -> bool {
self.entries.len() >= self.capacity
}
#[must_use]
pub const fn free(&self) -> usize {
self.capacity - self.entries.len()
}
#[must_use]
pub fn find_line_mut(&mut self, line: LineAddr) -> Option<&mut Mshr> {
self.entries.iter_mut().find(|m| m.line == line)
}
#[must_use]
pub fn holds(&self, line: LineAddr) -> bool {
self.entries.iter().any(|m| m.line == line)
}
pub fn allocate(&mut self, mshr: Mshr) {
debug_assert!(!self.is_full(), "MSHR allocation on a full table");
debug_assert!(!self.holds(mshr.line), "duplicate MSHR for a line");
self.entries.push(mshr);
}
pub fn take(&mut self, req_id: ReqId) -> Option<Mshr> {
let index = self.entries.iter().position(|m| m.req_id == req_id)?;
Some(self.entries.remove(index))
}
pub fn iter(&self) -> impl Iterator<Item = &Mshr> {
self.entries.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sim::components::CacheId;
fn mshr(line: u64, req: u64) -> Mshr {
Mshr {
line: LineAddr::from_phys(PhysAddr::new(line), 64),
req_id: ReqId::new(req),
targets: Vec::new(),
deferred: Vec::new(),
write: false,
prefetch: false,
issued_at: 0,
upgrade: false,
}
}
#[test]
fn allocation_is_bounded_and_take_frees_a_slot() {
let mut table = MshrTable::new(2);
table.allocate(mshr(0x1000, 1));
table.allocate(mshr(0x2000, 2));
assert!(table.is_full());
assert_eq!(table.free(), 0);
assert!(table.holds(LineAddr::from_phys(PhysAddr::new(0x1010), 64)));
let taken = table.take(ReqId::new(1)).expect("req 1 outstanding");
assert_eq!(taken.line, LineAddr::from_phys(PhysAddr::new(0x1000), 64));
assert_eq!(table.free(), 1);
assert!(table.take(ReqId::new(1)).is_none());
}
#[test]
fn zero_capacity_still_allows_one_miss() {
let table = MshrTable::new(0);
assert_eq!(table.capacity(), 1);
assert!(!table.is_full());
}
#[test]
fn targets_join_the_mshr_for_their_line() {
let mut table = MshrTable::new(2);
table.allocate(mshr(0x1000, 1));
let entry = table
.find_line_mut(LineAddr::from_phys(PhysAddr::new(0x1008), 64))
.expect("line in flight");
entry.targets.push(MshrTarget {
source: ComponentId::Cache(CacheId::new(0)),
req_id: ReqId::new(9),
paddr: PhysAddr::new(0x1008),
vaddr: None,
pc: None,
size: AccessSize::B8,
op: MemOp::Write {
data: crate::sim::packet::WriteData::Small(1),
origin: crate::sim::packet::WriteOrigin::Hart(crate::common::HartId::new(0)),
},
});
entry.write = true;
assert_eq!(table.iter().next().map(|m| m.targets.len()), Some(1));
}
}