-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6ff7516
commit a00f580
Showing
7 changed files
with
292 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |