Skip to content

Commit

Permalink
Implement write-back cache and refactor write-through cache (#9)
Browse files Browse the repository at this point in the history
* Implement write-back cache and refactor write-through cache

* Implement review
  • Loading branch information
hackenbergstefan authored Mar 1, 2024
1 parent 7f3deb5 commit 25bbdbe
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 47 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

[package]
name = "rainbow-rs"
version = "0.5.0"
version = "0.6.0"
edition = "2021"

[dependencies]
Expand Down
19 changes: 15 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ use rainbow_rs::{
leakage::{
ElmoPowerLeakage, HammingDistanceLeakage, HammingWeightLeakage, PessimisticHammingLeakage,
},
memory_extension::{BusNoCache, CacheLru, NoBusNoCache, MAX_BUS_SIZE, MAX_CACHE_LINES},
memory_extension::{
BusNoCache, CacheLruWriteBack, CacheLruWriteThrough, NoBusNoCache, MAX_BUS_SIZE,
MAX_CACHE_LINES,
},
new_simpleserialsocket_stm32f4, ThumbTraceEmulatorTrait,
};

Expand All @@ -37,7 +40,8 @@ enum LeakageModel {
enum MemoryExtension {
NoBusNoCache,
BusNoCache,
CacheLru,
CacheLruWriteThrough,
CacheLruWriteBack,
}

#[derive(Parser, Debug)]
Expand Down Expand Up @@ -222,9 +226,16 @@ fn main() -> Result<()> {
MemoryExtension::BusNoCache => {
Box::new(BusNoCache::new(args.memory_buswidth))
}
MemoryExtension::CacheLru => {
Box::new(CacheLru::new(args.memory_buswidth, args.memory_cache_lines))
MemoryExtension::CacheLruWriteThrough => {
Box::new(CacheLruWriteThrough::new(
args.memory_buswidth,
args.memory_cache_lines,
))
}
MemoryExtension::CacheLruWriteBack => Box::new(CacheLruWriteBack::new(
args.memory_buswidth,
args.memory_cache_lines,
)),
},
client.clone(),
)?;
Expand Down
225 changes: 188 additions & 37 deletions src/memory_extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,14 @@ impl MemoryExtension for BusNoCache {
}
}

pub struct CacheLru {
pub struct CacheLruWriteThrough {
bus_size: usize,
bus: [u8; MAX_BUS_SIZE],
cache_lines: usize,
cache: ArrayVec<(u64, [u8; MAX_BUS_SIZE]), MAX_CACHE_LINES>,
}

impl CacheLru {
impl CacheLruWriteThrough {
pub fn new(bus_size: usize, cache_lines: usize) -> Self {
Self {
bus_size,
Expand All @@ -129,15 +129,41 @@ impl CacheLru {
cache: ArrayVec::new(),
}
}

/// Update bus. Leaks: Bus
fn update_and_leak_bus(&mut self, scadata: &mut ScaData, newvalue: [u8; MAX_BUS_SIZE]) {
scadata.bus_updates.push((self.bus, newvalue));
self.bus = newvalue;
}

/// Update cache line with new content.
/// Leaks: Cache (may), Bus[write] (may), Memory (may)
fn update_and_leak_cache_bus_memory(
&mut self,
scadata: &mut ScaData,
index: usize,
newcontent: [u8; MAX_BUS_SIZE],
) {
let line = &self.cache[index];
if line.1 == newcontent {
return;
}
let (address, oldcontent) = self.cache.pop_at(index).unwrap();

scadata.cache_updates.push((oldcontent, newcontent));
scadata.bus_updates.push((oldcontent, newcontent));
scadata.memory_updates.push((oldcontent, newcontent));
self.cache.push((address, newcontent));
}
}

/// Least Recently Used (LRU) cache implementation.
/// Least Recently Used (LRU) cache implementation with write-through (https://stackoverflow.com/a/27161893).
/// On a memory update the cache behaves as follows:
/// 1. If the address is in the cache, the cache line is updated (and moved to end)
/// and the bus is updated if necessary.
/// and the bus and memory are updated if necessary.
/// 2. If the address is not in the cache, the last cache line is removed if necessary,
/// the new cache line is added and the bus is updated if necessary.
impl MemoryExtension for CacheLru {
/// the new cache line is added and the bus and memory are updated if necessary.
impl MemoryExtension for CacheLruWriteThrough {
#[inline]
fn bus_size(&self) -> usize {
self.bus_size
Expand Down Expand Up @@ -165,43 +191,168 @@ impl MemoryExtension for CacheLru {
self.cache[index].1,
memory_before
);

// Update cache line and bus if necessary
if memory_before != memory_after {
scadata
.cache_updates
.push((self.cache[index].1, memory_after));
self.cache.remove(index);
self.cache.push((address, memory_after));
scadata.bus_updates.push((self.bus, memory_after));
self.bus = memory_after;
}
self.update_and_leak_cache_bus_memory(scadata, index, memory_after);
} else {
// Remove last cache line if necessary
if self.cache.len() == self.cache_lines {
scadata.cache_updates.push((self.cache[0].1, memory_before));
self.cache.remove(0);
} else {
scadata
.cache_updates
.push(([0; MAX_BUS_SIZE], memory_before));
}
// 1. Read new memory. Leaks: Bus[read]
self.update_and_leak_bus(scadata, memory_before);

// 2. Update cache. Leaks: Cache
let oldline_content = {
if self.cache.len() == self.cache_lines {
// 2a. Remove last cache line
let (_, oldline_content) = self.cache.pop_at(0).unwrap();
oldline_content
} else {
// 2b. Add new line
[0; MAX_BUS_SIZE]
}
};
scadata.cache_updates.push((oldline_content, memory_before));
self.cache.push((address, memory_before));

// Update cache line and bus if necessary
if memory_before != memory_after {
let last = self.cache.last_mut().unwrap();
scadata.cache_updates.push((last.1, memory_after));
last.1 = memory_after;
scadata.bus_updates.push((self.bus, memory_after));
self.bus = memory_after;
}
// 3. Update cache line and bus if necessary. Leaks: Cache (may), Bus (may), Memory (may)
self.update_and_leak_cache_bus_memory(scadata, self.cache.len() - 1, memory_after);
}
trace!("Cache: {:x?}", self.cache);
}
}

// Update memory
if memory_before != memory_after {
scadata.memory_updates.push((memory_before, memory_after));
#[derive(Debug)]
struct CacheLruWriteBackCacheLine {
address: u64,
content: [u8; MAX_BUS_SIZE],
memory: [u8; MAX_BUS_SIZE],
dirty: bool,
}

pub struct CacheLruWriteBack {
bus_size: usize,
bus: [u8; MAX_BUS_SIZE],
cache_lines: usize,
cache: ArrayVec<CacheLruWriteBackCacheLine, MAX_CACHE_LINES>,
}

impl CacheLruWriteBack {
pub fn new(bus_size: usize, cache_lines: usize) -> Self {
Self {
bus_size,
bus: [0; MAX_BUS_SIZE],
cache_lines,
cache: ArrayVec::new(),
}
}

/// Update bus. Leaks: Bus
fn update_and_leak_bus(&mut self, scadata: &mut ScaData, newvalue: [u8; MAX_BUS_SIZE]) {
scadata.bus_updates.push((self.bus, newvalue));
self.bus = newvalue;
}

/// Update cache line with new content.
/// Leaks: Cache (may)
fn update_and_leak_cache(
&mut self,
scadata: &mut ScaData,
index: usize,
newcontent: [u8; MAX_BUS_SIZE],
) {
let line = &self.cache[index];
if line.content == newcontent {
return;
}

scadata.cache_updates.push((line.content, newcontent));
let newline = CacheLruWriteBackCacheLine {
address: line.address,
content: newcontent,
memory: line.memory,
dirty: true,
};
self.cache.remove(index);
self.cache.push(newline);
}

/// Execute write-back:
/// 1. Pop lru cache line
/// 2. If line is dirty, leak bus and memory
fn write_back_and_leak(&mut self, scadata: &mut ScaData) -> CacheLruWriteBackCacheLine {
let oldline = self.cache.pop_at(0).unwrap();
if oldline.dirty {
self.update_and_leak_bus(scadata, oldline.content);
scadata
.memory_updates
.push((oldline.memory, oldline.content));
}
oldline
}
}

/// Least Recently Used (LRU) cache implementation with write-through (https://stackoverflow.com/a/27161893).
/// The memory is modelled as read-modify-write independently of actual content changes.
/// On an update the cache behaves as follows:
/// 1. If the address is in the cache, the cache line is updated (and moved to end).
/// 2. If the address is not in the cache, the last cache line is removed if necessary,
/// the new cache line is added and updated if necessary.
/// 3. If the line pops out of the cache and it is written back to bus and memory if necessary.
impl MemoryExtension for CacheLruWriteBack {
#[inline]
fn bus_size(&self) -> usize {
self.bus_size
}

fn reset(&mut self) {
self.cache.clear();
self.bus = [0; MAX_BUS_SIZE];
}

fn update(
&mut self,
scadata: &mut ScaData,
_memtype: MemType,
address: u64,
memory_before: [u8; MAX_BUS_SIZE],
memory_after: [u8; MAX_BUS_SIZE],
) {
assert!((address as usize % self.bus_size) == 0);
// Check if address is in cache
if let Some(index) = self.cache.iter().position(|line| line.address == address) {
let line = &self.cache[index];
assert!(
line.content == memory_before,
"Cache line {index} at {address:08x} is not up to date: {:x?} != {:x?}",
line.content,
memory_before
);

// Update cache line. Leaks: cache (may)
self.update_and_leak_cache(scadata, index, memory_after);
} else {
let oldline_content = {
// 1a. Remove lru cache line. Leaks: cache, bus[write] (may), memory (may)
if self.cache.len() == self.cache_lines {
let oldline = self.write_back_and_leak(scadata);
oldline.content
// 1b. Leaks: cache
} else {
[0; MAX_BUS_SIZE]
}
};

// 2. "Read" from memory. Leaks: bus[read] and add new cache line
self.update_and_leak_bus(scadata, memory_before);

// 3. Add new cache line. Leaks: Cache
scadata.cache_updates.push((oldline_content, memory_before));
self.cache.push(CacheLruWriteBackCacheLine {
address,
content: memory_before,
memory: memory_before,
dirty: false,
});

// 4. Update cache line if necessary. Leaks: Cache (may)
self.update_and_leak_cache(scadata, self.cache.len() - 1, memory_after);
}
trace!("Cache: {:x?}", self.cache);
}
}
Loading

0 comments on commit 25bbdbe

Please sign in to comment.