use rustv::isa; use rustv::memory::{MemoryError, MemoryInterface, Result, SharedMemory}; pub const WRITE_TRAP_VALUE: u8 = 0x42; pub const AREA_TRAP_VALUE: u8 = 0x43; pub const WRITE_TRAP_STALL: u32 = 1_000_000; /// A cache that can be used as two separate caches or one /// set-associative cache. pub struct ShareableCache<'a> { core_id: usize, primary: SharedMemory<'a>, secondary: SharedMemory<'a>, secondary_enabled: bool, use_secondary: bool, } impl<'a> ShareableCache<'a> { pub fn new(core_id: usize, cache1: SharedMemory<'a>, cache2: SharedMemory<'a>) -> ShareableCache<'a> { ShareableCache { core_id: core_id, primary: cache1.clone(), secondary: cache2.clone(), secondary_enabled: false, use_secondary: false, } } pub fn enable_secondary(&mut self) { self.secondary_enabled = true; self.use_secondary = true; } pub fn disable_secondary(&mut self) { self.secondary_enabled = false; } fn address_accessible(&self, address: isa::Address) -> (bool, bool) { // Use scopes to make sure these borrows end before the // branches of the if statement begin let primary_accessible = { self.primary.borrow().is_address_accessible(address) }; let secondary_accessible = { self.secondary.borrow().is_address_accessible(address) }; (primary_accessible, secondary_accessible) } } fn snoop(cache: &mut SharedMemory<'a>, address: isa::Address, value: isa::Word) { // Cache snooping: update all cache lines when a write is made if cache.borrow().is_address_accessible(address) { // Depends on invariant: write_word completes instantly // when the address is accessible (in-cache) let _ = cache.borrow_mut().write_word(address, value); } } fn as_bytes(word: isa::Word) -> [isa::Byte; 4] { [(word & 0xFF) as isa::Byte, ((word >> 8) & 0xFF) as isa::Byte, ((word >> 16) & 0xFF) as isa::Byte, ((word >> 24) & 0xFF) as isa::Byte] } fn check_traps(cache: &mut MemoryInterface, address: isa::Address, value: isa::Word) -> Result<()> { if cache.is_address_accessible(address) { // No stall - check for trap let old_value = cache.read_word(address); match old_value { Ok(old_value) => { // TODO: helper method on isa::Word (would require // newtype) for accessing individual bytes? let old_bytes = as_bytes(old_value); let new_bytes = as_bytes(value); let mut traps_set = 0; let mut traps_hit = 0; for (old, new) in old_bytes.iter().zip(new_bytes.iter()) { if *old == WRITE_TRAP_VALUE && *new != WRITE_TRAP_VALUE { traps_hit += 1; } else if *old != WRITE_TRAP_VALUE && *new == WRITE_TRAP_VALUE { traps_set += 1; } } if traps_hit > 0 { info!("[memory] {} write trap(s) hit at address {:x},\ stalling for 1_000_000 cycles each", traps_hit, address); Err(MemoryError::CacheMiss { stall_cycles: traps_hit * WRITE_TRAP_STALL, }) } else { cache.cache.write_word(address, value) } } Err(e) => { panic!("Could not read accessible value: {:?}", e) } } } else { // Not in cache - defer to fetch cache.cache.write_word(address, value) } } impl<'a> MemoryInterface for ShareableCache<'a> { fn latency(&self) -> u32 { self.primary.borrow().latency() } fn step(&mut self) { // We only step the primary cache. The idea is that the // secondary cache should be the primary cache of another // ShareableCache. self.primary.borrow_mut().step(); } fn is_address_accessible(&self, address: isa::Address) -> bool { self.primary.borrow().is_address_accessible(address) || (self.secondary_enabled && self.secondary.borrow().is_address_accessible(address)) } fn read_word(&mut self, address: isa::Address) -> Result { // TODO: disallow access to high or low memory unless // secondary cache is enabled. Remember: addresses are already // translated // TODO: is CacheRacer physically or virtually addressed? if self.secondary_enabled { let (primary_accessible, secondary_accessible) = self.address_accessible(address); if primary_accessible { self.primary.borrow_mut().read_word(address) } else if secondary_accessible { self.secondary.borrow_mut().read_word(address) } else { self.use_secondary = !self.use_secondary; if self.use_secondary { self.secondary.borrow_mut().read_word(address) } else { self.primary.borrow_mut().read_word(address) } } } else { self.primary.borrow_mut().read_word(address) } } fn write_word(&mut self, address: isa::Address, value: isa::Word) -> Result<()> { if self.secondary_enabled { let (primary_accessible, secondary_accessible) = self.address_accessible(address); if primary_accessible { snoop(&mut self.secondary, address, value); check_traps(&self.primary.borrow_mut(), address, value) } else if secondary_accessible { snoop(&mut self.primary, address, value); check_traps(&self.secondary.borrow_mut(), address, value) } else { self.use_secondary = !self.use_secondary; if self.use_secondary { snoop(&mut self.primary, address, value); check_traps(&self.secondary.borrow_mut(), address, value) } else { snoop(&mut self.secondary, address, value); check_traps(&self.primary.borrow_mut(), address, value) } } } else { snoop(&mut self.secondary, address, value); check_traps(&self.primary.borrow_mut(), address, value) } } }