use std::collections::HashSet; use rustv::isa::{self, IsaType}; use rustv::memory::{MemoryError, MemoryInterface, Result, SharedMemory}; pub const WRITE_TRAP_VALUE: isa::Byte = isa::Byte(0xE0); pub const AREA_TRAP_VALUE: isa::Byte = isa::Byte(0xE4); pub const WRITE_TRAP_STALL: u32 = 1_000_000; pub const WRITE_TRAP_SET_STALL: u32 = 100; /// 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, traps_hit: HashSet, } // Cache snooping: update all cache lines when a write is made macro_rules! snoop { ($cache: expr, $write_value: ident, $address: ident, $value: ident) => { 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_value($address, $value); } } } macro_rules! check_traps { ($core_id: expr, $cache: expr, $traps_hit: expr, $write_value: ident, $address: ident, $value: ident) => {{ // Requires invariant: if x is the address of a word, x + 0 is // the address of the LSB and x + 3 is the address of the MSB. let accessible = { $cache.borrow().is_address_accessible($address) }; if accessible { // No stall - check for trap let old_value = { $cache.borrow_mut().read_word($address) }; match old_value { Ok(old_value) => { let old_bytes = old_value.as_bytes(); let new_bytes = $value.as_bytes(); let mut num_traps_hit = 0; let mut num_traps_set = 0; let iter = old_bytes.iter() // Skip {offset} bytes .skip(($address.0 & 0x3) as usize) .take(new_bytes.len()) .zip(new_bytes.iter()) .enumerate(); for (offset, (old, new)) in iter { // Make sure offset bits are cleared before // adding offset let trap_address = ($address & 0xFFFFFFFC) + isa::Word(offset as u32); if *old == WRITE_TRAP_VALUE && !$traps_hit.contains(&trap_address) { num_traps_hit += 1; $traps_hit.insert(trap_address); } else if *old != WRITE_TRAP_VALUE { $traps_hit.remove(&trap_address); } if *new == WRITE_TRAP_VALUE && (*old != WRITE_TRAP_VALUE || $traps_hit.contains(&trap_address)) { $traps_hit.remove(&trap_address); num_traps_set += 1; } } if num_traps_hit > 0 { info!("[memory] core {}: {} write trap(s) hit at address {:x},\ stalling for 1_000_000 cycles each", $core_id, num_traps_hit, $address); Err(MemoryError::CacheMiss { stall_cycles: num_traps_hit * WRITE_TRAP_STALL, retry: true, }) } else { // Use retry: false to induce a stall iff the // write succeeded let result = $cache.borrow_mut().$write_value($address, $value); if num_traps_set > 0 { match result { Ok(()) => { info!("[memory] core {}: {} write trap(s) set at address {:x},\ stalling for 100 cycles each", $core_id, num_traps_set, $address); Err(MemoryError::CacheMiss { stall_cycles: num_traps_set * WRITE_TRAP_SET_STALL, retry: false, }) }, _ => result } } else { result } } } Err(e) => { panic!("Could not read accessible value: {:?}", e) } } } else { // Not in cache - defer to fetch $cache.borrow_mut().$write_value($address, $value) } }} } macro_rules! write_value { ($self_: ident, $write_value: ident, $address: ident, $value: ident) => { if $self_.secondary_enabled { let (primary_accessible, secondary_accessible) = $self_.address_accessible($address); if primary_accessible { snoop!($self_.secondary, $write_value, $address, $value); check_traps!($self_.core_id, $self_.primary, &mut $self_.traps_hit, $write_value, $address, $value) } else if secondary_accessible { snoop!($self_.primary, $write_value, $address, $value); check_traps!($self_.core_id, $self_.secondary, &mut $self_.traps_hit, $write_value, $address, $value) } else { $self_.use_secondary = !$self_.use_secondary; if $self_.use_secondary { snoop!($self_.primary, $write_value, $address, $value); check_traps!($self_.core_id, $self_.secondary, &mut $self_.traps_hit, $write_value, $address, $value) } else { snoop!($self_.secondary, $write_value, $address, $value); check_traps!($self_.core_id, $self_.primary, &mut $self_.traps_hit, $write_value, $address, $value) } } } else { snoop!($self_.secondary, $write_value, $address, $value); check_traps!($self_.core_id, $self_.primary, &mut $self_.traps_hit, $write_value, $address, $value) } } } 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, traps_hit: HashSet::new(), } } 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) } } 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<()> { write_value!(self, write_word, address, value) } fn write_halfword(&mut self, address: isa::Address, value: isa::HalfWord) -> Result<()> { write_value!(self, write_halfword, address, value) } fn write_byte(&mut self, address: isa::Address, value: isa::Byte) -> Result<()> { write_value!(self, write_byte, address, value) } }