Skip to content

Commit

Permalink
Start working on cache
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanuppal committed Jun 15, 2024
1 parent 6ff7516 commit a00f580
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 1 deletion.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ members = [
"tools/calyx-pass-explorer",
"tools/yxi",
"tools/preprocessor",
# "tools/cache",
"tools/cache",
]
exclude = ["site"]

Expand Down
15 changes: 15 additions & 0 deletions tools/cache/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "cache"
authors.workspace = true
license-file.workspace = true
keywords.workspace = true
repository.workspace = true
readme.workspace = true
description.workspace = true
categories.workspace = true
homepage.workspace = true
edition.workspace = true
version.workspace = true
rust-version.workspace = true

[dependencies]
8 changes: 8 additions & 0 deletions tools/cache/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
PREPROCESSOR := cargo run --manifest-path ../preprocessor/Cargo.toml --

cache.futil: src/main.rs
cargo run > $@

.PHONY: clean
clean:
rm -f cache.futil
33 changes: 33 additions & 0 deletions tools/cache/cache.futil
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// This file was generated
import "primitives/core.futil";
import "primitives/memories/seq.futil";

// dummy main
component main() -> () {
cells {}
wires {}
control {}
}

$define ADDRESS_WIDTH 16
$define EXTRA_BITS 2

$define L1_TAG_BITS 8
$define L1_INDEX_BITS 6
$define L1_BLOCKS_PER_SET 4
$define L1_BLOCK_SIZE 4

$define L1_ENTRY_BITS EXTRA_BITS + L1_TAG_BITS + L1_INDEX_BITS + L1_BLOCKS_PER_SET * L1_BLOCK_SIZE * 8
$define L1_NUM_ENTRIES 2 ^ L1_INDEX_BITS

component cache_level_L1(@go(1) read_go: 1, @go(2) write_go: 1) -> (
@done(1) read_done: 1,
@done(2) write_done: 1
) {
cells {
entries = seq_mem_d1($L1_ENTRY_BITS, $L1_NUM_ENTRIES, $L1_INDEX_BITS);
}
wires {}
control {}
}

32 changes: 32 additions & 0 deletions tools/cache/resources/cache_template.futil
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import "primitives/core.futil";
import "primitives/memories/seq.futil";

component main() -> () {
cells {}
wires {}
control {}
}

$define ADDRESS_BITS 16
$define EXTRA_BITS 2

$define TAG_BITS 10
$define INDEX_BITS 3
$define BLOCKS_PER_SET 4
$define BLOCK_SIZE 8

// END_SKIP

$define ENTRY_BITS EXTRA_BITS + TAG_BITS + INDEX_BITS + BLOCKS_PER_SET * BLOCK_SIZE * 8
$define NUM_ENTRIES 2 ^ INDEX_BITS

component NAME_MARKER(@go(1) read_go: 1, @go(2) write_go: 1) -> (
@done(1) read_done: 1,
@done(2) write_done: 1
) {
cells {
entries = seq_mem_d1($ENTRY_BITS, $NUM_ENTRIES, $INDEX_BITS);
}
wires {}
control {}
}
199 changes: 199 additions & 0 deletions tools/cache/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
const CACHE_TEMPLATE: &str = include_str!("../resources/cache_template.futil");
const END_SKIP_MARKER: &str = "// END_SKIP";
const COMPONENT_NAME_MARKER: &str = "component NAME_MARKER";

/// The number of bits needed to represent `x` distinct values.
fn get_repr_bits(x: usize) -> usize {
x.next_power_of_two().trailing_zeros() as usize
}

trait MultilineFormatting: AsRef<str> {
fn format_multiline(&self) -> String {
let mut lines = self
.as_ref()
.lines()
.skip_while(|line| line.chars().all(char::is_whitespace));
if let Some(first_line) = lines.next() {
let to_strip =
first_line.chars().take_while(|c| c.is_whitespace()).count();
let mut result = String::new();
for line in std::iter::once(first_line).chain(lines) {
result.push_str(
&line.trim_end().chars().skip(to_strip).collect::<String>(),
);
result.push('\n');
}
result
} else {
String::new()
}
}
}

impl MultilineFormatting for str {}

#[cfg(test)]
mod tests {
use crate::get_repr_bits;

#[test]
fn test_get_repr_bits() {
assert_eq!(0, get_repr_bits(0), "no values need no bits");
assert_eq!(0, get_repr_bits(1), "1 value needs no bits");
assert_eq!(1, get_repr_bits(2), "2 values need 1 bit");
assert_eq!(2, get_repr_bits(3), "3 values need 2 bits");
assert_eq!(2, get_repr_bits(4), "4 values need 2 bits");
assert_eq!(4, get_repr_bits(16), "16 values need 4 bits");
assert_eq!(10, get_repr_bits(1024), "1024 values need 10 bits");
}
}

struct CacheLevel<'a> {
name: &'a str,
tag_bits: usize,
index_bits: usize,
k: usize,
block_size: usize,
}

impl CacheLevel<'_> {
fn build(&self) -> String {
let mut result = String::new();
let mut content = String::new();

let value_defines = [
("TAG_BITS", self.tag_bits),
("INDEX_BITS", self.index_bits),
("BLOCKS_PER_SET", self.k),
("BLOCK_SIZE", self.block_size),
];
let other_defines = ["NUM_ENTRIES", "ENTRY_BITS"];

for line in &CACHE_TEMPLATE
.lines()
.skip_while(|line| !line.starts_with(END_SKIP_MARKER))
.skip(1)
.collect::<Vec<_>>()
{
if line.starts_with(COMPONENT_NAME_MARKER) {
content.push_str(&format!(
"component cache_level_{}{}",
self.name,
line.chars()
.skip(COMPONENT_NAME_MARKER.len())
.collect::<String>()
));
} else {
content.push_str(line);
}
content.push('\n');
}

for define in value_defines
.iter()
.map(|(define, _)| *define)
.chain(other_defines)
{
content =
content.replace(define, &format!("{}_{}", self.name, define));
}

for (define, value) in value_defines {
result.push_str(&format!(
"$define {}_{} {}\n",
self.name, define, value
));
}
result.push_str(&content);

result
}
}

struct Cache<'a> {
address_width: usize,
levels: Vec<CacheLevel<'a>>,
}

impl<'a> Cache<'a> {
/// Begins a cache for a memory addressed by `address_width` bits. You
/// *must* call [`Cache::add_level`] at least once before you call
/// [`Cache::build`].
pub fn for_address_space(address_width: usize) -> Self {
Self {
address_width,
levels: vec![],
}
}

/// Adds a level below all current levels storing `size` bytes in sets of
/// `k` blocks, each of which can store `block_size` bytes.
///
/// Requires: `name` is a calyx identifier, `size` is a power of two, and
/// `k * block_size` divides `size`.
pub fn add_level<'b: 'a>(
mut self,
name: &'b str,
size: usize,
k: usize,
block_size: usize,
) -> Self {
assert!(
name.chars().next().map_or(true, |c| !c.is_numeric())
&& name.chars().all(|c| c.is_alphanumeric() || c == '_'),
"invalid name"
);
assert!(size.is_power_of_two(), "size not a power of 2");

let bytes_per_set = k * block_size;
assert!(size % bytes_per_set == 0, "blocks cannot fit into size");

let num_sets = size / bytes_per_set;

let index_bits = get_repr_bits(num_sets);
let offset_bits = get_repr_bits(block_size);
let tag_bits = self.address_width - index_bits - offset_bits;

self.levels.push(CacheLevel {
name,
tag_bits,
index_bits,
k,
block_size,
});
self
}

pub fn build(self) -> String {
assert!(!self.levels.is_empty(), "no levels were added");
let mut result = "
// This file was generated
import \"primitives/core.futil\";
import \"primitives/memories/seq.futil\";
// dummy main
component main() -> () {
cells {}
wires {}
control {}
}
"
.format_multiline();
result.push_str(&format!(
"$define ADDRESS_WIDTH {}\n$define EXTRA_BITS 2\n\n",
self.address_width
));
for level in self.levels {
result.push_str(&level.build());
}
result
}
}

fn main() {
const ADDRESS_WIDTH: usize = 16;
let cache = Cache::for_address_space(ADDRESS_WIDTH)
.add_level("L1", 1024, 4, 4)
.build();
println!("{}", cache);
}

0 comments on commit a00f580

Please sign in to comment.