diff --git a/Cargo.lock b/Cargo.lock index 42aef1b1f6..b6143151aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -363,6 +363,10 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "cache" +version = "0.7.1" + [[package]] name = "calyx" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index 05bd00332c..692c122f67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ members = [ "tools/calyx-pass-explorer", "tools/yxi", "tools/preprocessor", - # "tools/cache", + "tools/cache", ] exclude = ["site"] diff --git a/tools/cache/Cargo.toml b/tools/cache/Cargo.toml new file mode 100644 index 0000000000..7898d06840 --- /dev/null +++ b/tools/cache/Cargo.toml @@ -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] diff --git a/tools/cache/Makefile b/tools/cache/Makefile new file mode 100644 index 0000000000..b028d98aa1 --- /dev/null +++ b/tools/cache/Makefile @@ -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 diff --git a/tools/cache/cache.futil b/tools/cache/cache.futil new file mode 100644 index 0000000000..f53f31cba2 --- /dev/null +++ b/tools/cache/cache.futil @@ -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 {} +} + diff --git a/tools/cache/resources/cache_template.futil b/tools/cache/resources/cache_template.futil new file mode 100644 index 0000000000..902c53f433 --- /dev/null +++ b/tools/cache/resources/cache_template.futil @@ -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 {} +} diff --git a/tools/cache/src/main.rs b/tools/cache/src/main.rs new file mode 100644 index 0000000000..e849daa35b --- /dev/null +++ b/tools/cache/src/main.rs @@ -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 { + 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::(), + ); + 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::>() + { + if line.starts_with(COMPONENT_NAME_MARKER) { + content.push_str(&format!( + "component cache_level_{}{}", + self.name, + line.chars() + .skip(COMPONENT_NAME_MARKER.len()) + .collect::() + )); + } 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>, +} + +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); +}