diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1a9f9f5963..f796428231 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -20,8 +20,6 @@ jobs: uses: actions/checkout@v4 - name: Install Rust stable uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - toolchain: stable - name: Build calyx dev run: cargo build - name: Check calyx build diff --git a/.gitignore b/.gitignore index 5c6840dac6..17a3384a44 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,5 @@ frontends/queues/tests/**/*.expect # emacs *~ + +!docs/dev/assets/*.png diff --git a/Cargo.lock b/Cargo.lock index 7b5156844a..e5db4631e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -429,6 +429,7 @@ version = "0.7.1" dependencies = [ "atty", "itertools 0.11.0", + "lazy_static", "petgraph", "serde", "serde_json", @@ -544,6 +545,7 @@ dependencies = [ "argh", "interp", "itertools 0.11.0", + "nom 7.1.3", "num-bigint", "num-rational", "num-traits", @@ -1461,7 +1463,6 @@ dependencies = [ "fraction", "fst-writer", "itertools 0.11.0", - "lazy_static", "num-bigint", "num-traits", "once_cell", @@ -1623,6 +1624,12 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -1674,6 +1681,16 @@ dependencies = [ "version_check 0.1.5", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2409,7 +2426,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5318bfeed779c64075ce317c81462ed54dc00021be1c6b34957d798e11a68bdb" dependencies = [ - "nom", + "nom 4.2.3", "serde", ] diff --git a/calyx-frontend/src/parser.rs b/calyx-frontend/src/parser.rs index ce1044e934..47938508dc 100644 --- a/calyx-frontend/src/parser.rs +++ b/calyx-frontend/src/parser.rs @@ -60,10 +60,13 @@ impl CalyxParser { let file = GlobalPositionTable::as_mut() .add_file(path.to_string_lossy().to_string(), string_content); let user_data = UserData { file }; - let content = GlobalPositionTable::as_ref().get_source(file); + let gpos_ref = GlobalPositionTable::as_ref(); + let content = gpos_ref.get_source(file).to_owned(); + drop(gpos_ref); + // Parse the file let inputs = - CalyxParser::parse_with_userdata(Rule::file, content, user_data) + CalyxParser::parse_with_userdata(Rule::file, &content, user_data) .map_err(|e| e.with_path(&path.to_string_lossy())) .map_err(|e| { calyx_utils::Error::parse_error(e.variant.message()) @@ -96,10 +99,13 @@ impl CalyxParser { let file = GlobalPositionTable::as_mut().add_file("".to_string(), buf); let user_data = UserData { file }; - let contents = GlobalPositionTable::as_ref().get_source(file); + let gpos_ref = GlobalPositionTable::as_ref(); + let content = gpos_ref.get_source(file).to_owned(); + drop(gpos_ref); + // Parse the input let inputs = - CalyxParser::parse_with_userdata(Rule::file, contents, user_data) + CalyxParser::parse_with_userdata(Rule::file, &content, user_data) .map_err(|e| { calyx_utils::Error::parse_error(e.variant.message()) .with_pos(&Self::error_span(&e, file)) diff --git a/calyx-utils/Cargo.toml b/calyx-utils/Cargo.toml index da438c0d4c..6d3dc94693 100644 --- a/calyx-utils/Cargo.toml +++ b/calyx-utils/Cargo.toml @@ -23,3 +23,4 @@ string-interner.workspace = true itertools.workspace = true petgraph.workspace = true symbol_table = { version = "0.3", features = ["global"] } +lazy_static.workspace = true diff --git a/calyx-utils/src/errors.rs b/calyx-utils/src/errors.rs index b087d017aa..b560fdeec0 100644 --- a/calyx-utils/src/errors.rs +++ b/calyx-utils/src/errors.rs @@ -179,7 +179,7 @@ impl Error { post_msg: None, } } - pub fn location(&self) -> (&str, usize, usize) { + pub fn location(&self) -> (String, usize, usize) { self.pos.get_location() } pub fn message(&self) -> String { diff --git a/calyx-utils/src/position.rs b/calyx-utils/src/position.rs index 2b74e99a24..fb5333897f 100644 --- a/calyx-utils/src/position.rs +++ b/calyx-utils/src/position.rs @@ -1,7 +1,12 @@ //! Definitions for tracking source position information of Calyx programs use itertools::Itertools; -use std::{cmp, fmt::Write, mem, sync}; +use lazy_static::lazy_static; +use std::{ + cmp, + fmt::Write, + sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}, +}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] /// Handle to a position in a [PositionTable] @@ -99,28 +104,28 @@ impl PositionTable { /// The global position table pub struct GlobalPositionTable; +lazy_static! { + static ref GPOS_TABLE: RwLock = + RwLock::new(PositionTable::default()); +} + impl GlobalPositionTable { - /// Return reference to a global [PositionTable] - pub fn as_mut() -> &'static mut PositionTable { - static mut SINGLETON: mem::MaybeUninit = - mem::MaybeUninit::uninit(); - static ONCE: sync::Once = sync::Once::new(); - - // SAFETY: - // - writing to the singleton is OK because we only do it one time - // - the ONCE guarantees that SINGLETON is init'ed before assume_init_ref - unsafe { - ONCE.call_once(|| { - SINGLETON.write(PositionTable::new()); - assert!(PositionTable::UNKNOWN == GPosIdx::UNKNOWN.0) - }); - SINGLETON.assume_init_mut() - } + /// Return reference to a global [PositionTable]. + /// + /// # Safety + /// + /// You may not call this function after any call to [`Self::as_ref`]. + pub fn as_mut() -> RwLockWriteGuard<'static, PositionTable> { + GPOS_TABLE + .try_write() + .expect("failed to get write lock for global position table") } /// Return an immutable reference to the global position table - pub fn as_ref() -> &'static PositionTable { - Self::as_mut() + pub fn as_ref() -> RwLockReadGuard<'static, PositionTable> { + GPOS_TABLE + .try_read() + .expect("failed to get read lock for global position table") } } @@ -152,7 +157,7 @@ impl GPosIdx { /// 1. lines associated with this span /// 2. start position of the first line in span /// 3. line number of the span - fn get_lines(&self) -> (Vec<&str>, usize, usize) { + fn get_lines(&self) -> (Vec, usize, usize) { let table = GlobalPositionTable::as_ref(); let pos_d = table.get_pos(self.0); let file = &table.get_file_data(pos_d.file).source; @@ -181,16 +186,20 @@ impl GPosIdx { pos = next_pos + 1; linum += 1; } - (buf, out_idx, out_line) + ( + buf.into_iter().map(|str| str.to_owned()).collect(), + out_idx, + out_line, + ) } /// returns: /// 1. the name of the file the span is in /// 2. the (inclusive) range of lines within the span - pub fn get_line_num(&self) -> (&String, (usize, usize)) { + pub fn get_line_num(&self) -> (String, (usize, usize)) { let table = GlobalPositionTable::as_ref(); let pos_data = table.get_pos(self.0); - let file_name = &table.get_file_data(pos_data.file).name; + let file_name = table.get_file_data(pos_data.file).name.clone(); let (buf, _, line_num) = self.get_lines(); //reformat to return the range (inclusive) let rng = (line_num, line_num + buf.len() - 1); @@ -205,7 +214,7 @@ impl GPosIdx { let (lines, pos, linum) = self.get_lines(); let mut buf = String::new(); - let l = lines[0]; + let l = lines[0].as_str(); let linum_text = format!("{} ", linum); let linum_space: String = " ".repeat(linum_text.len()); let mark: String = "^".repeat(cmp::min( @@ -238,17 +247,17 @@ impl GPosIdx { buf } - pub fn get_location(&self) -> (&str, usize, usize) { + pub fn get_location(&self) -> (String, usize, usize) { let table = GlobalPositionTable::as_ref(); let pos_d = table.get_pos(self.0); - let name = &table.get_file_data(pos_d.file).name; + let name = table.get_file_data(pos_d.file).name.clone(); (name, pos_d.start, pos_d.end) } /// Visualizes the span without any message or marking pub fn show(&self) -> String { let (lines, _, linum) = self.get_lines(); - let l = lines[0]; + let l = lines[0].as_str(); let linum_text = format!("{} ", linum); format!("{}|{}\n", linum_text, l) } diff --git a/docs/compiler.md b/docs/compiler.md index 088646d417..64b35aaac8 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -33,6 +33,7 @@ For example, the alias `all` is an ordered sequence of default passes executed when the compiler is run from the command-line. The command-line provides two options to control the execution of passes: + - `-p, --pass`: Execute this pass or alias. Overrides default alias. - `-d, --disable-pass`: Disable this pass or alias. Takes priority over `-p`. @@ -43,6 +44,10 @@ the default execution alias `all`: cargo run -- examples/futil/simple.futil -p all -d static-timing ``` +If you want to work with passes interactively (for instance, you only care about +a pass far into the `all` sequence, and it is impractical to pass 20 `-p` +options), you can [visualize them](./dev/calyx-pass-explorer.md) with the `calyx-pass-explorer` tool. + ## Providing Pass Options Some passes take options to control their behavior. The `--list-passes` command prints out the options for each pass. For example, the `tdcc` pass has the following options: @@ -53,23 +58,25 @@ tdcc: ``` The option allows us to change the behavior of the pass. To provide a pass-specific option, we use the `-x` switch: + ``` cargo run -- examples/futil/simple.futil -p tdcc -x tdcc:dump-fsm ``` Note that we specify the option of `tdcc` by prefixing it with the pass name and a colon. - ## Specifying Primitives Library The compiler implementation uses a standard library of components to compile programs. The only standard library for the compiler is located in: + ``` /primitives ``` Specify the location of the library using the `-l` flag: + ``` cargo run -- -l ./primitives ``` diff --git a/docs/dev/assets/calyx-missing.png b/docs/dev/assets/calyx-missing.png new file mode 100644 index 0000000000..06ab2f6730 Binary files /dev/null and b/docs/dev/assets/calyx-missing.png differ diff --git a/docs/dev/assets/horrific-interface.png b/docs/dev/assets/horrific-interface.png new file mode 100644 index 0000000000..e93a01a064 Binary files /dev/null and b/docs/dev/assets/horrific-interface.png differ diff --git a/docs/dev/assets/well-formed.png b/docs/dev/assets/well-formed.png new file mode 100644 index 0000000000..14af47f449 Binary files /dev/null and b/docs/dev/assets/well-formed.png differ diff --git a/docs/dev/calyx-pass-explorer.md b/docs/dev/calyx-pass-explorer.md index 8ff04e6016..a71112858f 100644 --- a/docs/dev/calyx-pass-explorer.md +++ b/docs/dev/calyx-pass-explorer.md @@ -9,6 +9,7 @@ is just to run it on code and see what happens. Enter [`calyx-pass-explorer`](https://github.com/calyxir/calyx/tree/main/tools/calyx-pass-explorer). It's a command line tool that provides an interactive interface for visualizing how different passes affect the source code. +It's been used to debug and develop new compiler passes as well as implement new features in the compiler, so I hope you can find it useful too! > ![Example running of the tool](https://raw.githubusercontent.com/calyxir/calyx/main/tools/calyx-pass-explorer/example_v0.0.0.png) > _The above image depicts the tool's interface in v0.0.0. @@ -67,11 +68,11 @@ tool to help develop it! We'll first run `calyx-pass-explorer example0.futil`. You should get something horrific like -![Lots of random text output that doesn't make sense](./assets/horrific-interface.png) +![Lots of random text output that doesn't make sense](assets/horrific-interface.png) > [!TIP] > If you get this message: -> ![Calyx executable could not be found](./assets/calyx-missing.png) +> ![Calyx executable could not be found](assets/calyx-missing.png) > You should setup `fud` or pass the path explicitly with `-e`, as suggested. > However, we're going to update this later to look at `fud2` as well because > `fud` is now officially deprecated. @@ -82,7 +83,7 @@ What we really want is to focus on what happens to, _e.g._, the `main` component To do that, we just pass `-c main` (or `--component main`) as a flag: ![Running the tool and visualizing how the well-formed pass affects the main -component](./assets/well-formed.png) +component](assets/well-formed.png) That's a lot better, but it's still quite a bit of information. Let's break it down. diff --git a/frontends/queues/README.md b/frontends/queues/README.md index 139e974843..882359c905 100644 --- a/frontends/queues/README.md +++ b/frontends/queues/README.md @@ -1,12 +1,35 @@ # Queues Library -See the [docs](https://docs.calyxir.org/frontends/queues.html) for more details. +See the [docs][docs] for more details. ## Installation To use our queues: -1. Install [flit](https://flit.readthedocs.io/en/latest/#install) +1. Install [flit][flit] 2. Install the `queues` package: ``` $ cd frontends/queues/ $ flit install --symlink +``` + +## Converting Tests to Calyx + +To convert any of our [randomized tests][testing-harness] to a single Calyx file and their associated data and expect files: + +0. Follow the [installation instructions](#installation) +1. Choose a test by picking a `.py` file in [`tests/`][tests-dir] +2. Convert the test to Calyx: +``` +python3 _test.py 20000 --keepgoing > _test.futil ``` +3. Run the script [`gen_test_data.sh`][gen_test_data.sh] to generate data and expect files: +``` +./gen_test_data.sh +``` + +The files `_test.py`, `_test.data`, and `_test.expect` contain the Calyx program, input data, and expected outputs for the test. + +[docs]: https://docs.calyxir.org/frontends/queues.html +[flit]: https://flit.readthedocs.io/en/latest/#install +[testing-harness]: https://docs.calyxir.org/frontends/queues.html#shared-testing-harness +[tests-dir]: ./tests/ +[gen_test_data.sh]: ./test_data_gen/gen_test_data.sh \ No newline at end of file diff --git a/interp/Cargo.toml b/interp/Cargo.toml index 8d99d1a848..49926126aa 100644 --- a/interp/Cargo.toml +++ b/interp/Cargo.toml @@ -16,7 +16,6 @@ path = "src/main.rs" [dependencies] smallvec = { workspace = true, features = ["union", "const_generics"] } serde = { workspace = true, features = ["derive", "rc"] } -lazy_static.workspace = true itertools.workspace = true pest.workspace = true pest_derive.workspace = true diff --git a/interp/src/as_raw.rs b/interp/src/as_raw.rs index dada77d2df..a14ac7d812 100644 --- a/interp/src/as_raw.rs +++ b/interp/src/as_raw.rs @@ -17,12 +17,12 @@ impl AsRaw for *const T { } } -impl<'a, T> AsRaw for &Ref<'a, T> { +impl AsRaw for &Ref<'_, T> { fn as_raw(&self) -> *const T { self as &T as *const T } } -impl<'a, T> AsRaw for Ref<'a, T> { +impl AsRaw for Ref<'_, T> { fn as_raw(&self) -> *const T { self as &T as *const T } diff --git a/interp/src/debugger/commands/core.rs b/interp/src/debugger/commands/core.rs index 37148ef8a0..f9267c5a13 100644 --- a/interp/src/debugger/commands/core.rs +++ b/interp/src/debugger/commands/core.rs @@ -1,7 +1,6 @@ //! This module contains the core data structures and commands used by the debugger use itertools::{self, Itertools}; -use lazy_static::lazy_static; use owo_colors::OwoColorize; use std::{ fmt::{Display, Write}, @@ -299,6 +298,37 @@ impl From<(Vec, Option, PrintMode)> for PrintTuple { } } +/// ParseNodes enum is used to represent what child to traverse with respect to +/// the current ControlIdx. +/// Body defines that we should go into the body of a while or repeat. +/// Offset defines which child to go to. +/// If defines whether we should go to the true or false branch next +#[derive(Debug, PartialEq, Clone)] +pub enum ParseNodes { + Body, + Offset(u32), + If(bool), +} +pub struct ParsePath { + nodes: Vec, +} + +impl ParsePath { + pub fn new(nodes: Vec) -> ParsePath { + ParsePath { nodes } + } + + pub fn get_path(&self) -> Vec { + self.nodes.clone() + } +} + +impl FromIterator for ParsePath { + fn from_iter>(iter: I) -> Self { + ParsePath::new(iter.into_iter().collect()) + } +} + // Different types of printing commands pub enum PrintCommand { Normal, @@ -372,7 +402,7 @@ impl Command { invocation: names, description: message, .. - } in COMMAND_INFO.iter() + } in get_command_info().iter() { writeln!(out, " {: <30}{}", names.join(", "), message.green()) .unwrap(); @@ -388,7 +418,9 @@ impl Command { invocation, description, usage_example, - } in COMMAND_INFO.iter().filter(|x| !x.usage_example.is_empty()) + } in get_command_info() + .iter() + .filter(|x| !x.usage_example.is_empty()) { writeln!(out).unwrap(); writeln!(out, "{}", invocation.join(", ")).unwrap(); @@ -407,11 +439,16 @@ impl Command { // I wouldn't recommend looking at this -lazy_static! { - /// A (lazy) static list of [CommandInfo] objects used for the help and - /// explain messages - static ref COMMAND_INFO: Vec = { - vec![ +use std::sync::OnceLock; +/// A (lazy) static list of [CommandInfo] objects used for the help and +/// explain messages. Access via [get_command_info] +static COMMAND_INFO: OnceLock> = OnceLock::new(); + +/// Returns the list of [CommandInfo] objects used for the help and explain +/// messages +fn get_command_info() -> &'static [CommandInfo] { + COMMAND_INFO.get_or_init(|| { + [ // step CIBuilder::new().invocation("step") .invocation("s") @@ -491,7 +528,6 @@ lazy_static! { .description("Disable target watchpoint") .usage("> disable-watch 4") .usage("> disable-watch do_mult").build(), - // explain CIBuilder::new().invocation("explain") .description("Show examples of commands which take arguments").build(), @@ -501,15 +537,15 @@ lazy_static! { CIBuilder::new().invocation("exit") .invocation("quit") .description("Exit the debugger").build(), - ] - }; + ].into() + }) } #[derive(Clone, Debug)] struct CommandInfo { - invocation: Vec, + invocation: Box<[CommandName]>, description: Description, - usage_example: Vec, + usage_example: Box<[UsageExample]>, } // type shenanigans @@ -596,9 +632,9 @@ where impl CommandInfoBuilder { fn build(self) -> CommandInfo { CommandInfo { - invocation: self.invocation, + invocation: self.invocation.into(), description: self.description.unwrap(), - usage_example: self.usage_example, + usage_example: self.usage_example.into(), } } } diff --git a/interp/src/debugger/commands/mod.rs b/interp/src/debugger/commands/mod.rs index 452108f4ea..19f20de743 100644 --- a/interp/src/debugger/commands/mod.rs +++ b/interp/src/debugger/commands/mod.rs @@ -1,6 +1,7 @@ //! This module contains the structures for the debugger commands pub(crate) mod command_parser; pub mod core; +mod path_parser; pub use command_parser::parse_command; pub use core::Command; diff --git a/interp/src/debugger/commands/path_parser.pest b/interp/src/debugger/commands/path_parser.pest new file mode 100644 index 0000000000..f95eaff145 --- /dev/null +++ b/interp/src/debugger/commands/path_parser.pest @@ -0,0 +1,13 @@ +root = { "." } + +separator = { "-" } + +body = { "b" } + +num = { ASCII_DIGIT+ } + +branch = {"t" | "f"} + +clause = { separator ~ (body | num | branch) } + +path = { SOI ~ root ~ clause* ~ EOI } diff --git a/interp/src/debugger/commands/path_parser.rs b/interp/src/debugger/commands/path_parser.rs new file mode 100644 index 0000000000..5739da65b9 --- /dev/null +++ b/interp/src/debugger/commands/path_parser.rs @@ -0,0 +1,116 @@ +use super::{core::ParseNodes, ParsePath}; + +use pest_consume::{match_nodes, Error, Parser}; + +type ParseResult = std::result::Result>; +type Node<'i> = pest_consume::Node<'i, Rule, ()>; + +// include the grammar file so that Cargo knows to rebuild this file on grammar changes +const _GRAMMAR: &str = include_str!("path_parser.pest"); + +#[derive(Parser)] +#[grammar = "debugger/commands/path_parser.pest"] + +pub struct PathParser; + +#[pest_consume::parser] +impl PathParser { + fn EOI(_input: Node) -> ParseResult<()> { + Ok(()) + } + + fn root(_input: Node) -> ParseResult<()> { + Ok(()) + } + + fn body(_input: Node) -> ParseResult<()> { + Ok(()) + } + + fn separator(_input: Node) -> ParseResult<()> { + Ok(()) + } + + fn num(input: Node) -> ParseResult { + input + .as_str() + .parse::() + .map_err(|_| input.error("Expected non-negative number")) + } + + fn branch(input: Node) -> ParseResult { + let b = input.as_str(); + let result = b != "f"; + Ok(result) + } + + fn clause(input: Node) -> ParseResult { + Ok(match_nodes!(input.into_children(); + [separator(_), num(n)] => ParseNodes::Offset(n), + [separator(_), body(_)] => ParseNodes::Body, + [separator(_), branch(b)] => ParseNodes::If(b) + )) + } + + fn path(input: Node) -> ParseResult { + Ok(match_nodes!(input.into_children(); + [root(_), clause(c).., EOI(_)] => ParsePath::from_iter(c), + )) + } +} + +// Parse the path +#[allow(dead_code)] +pub fn parse_path(input_str: &str) -> Result>> { + let entries = PathParser::parse(Rule::path, input_str)?; + let entry = entries.single()?; + + PathParser::path(entry).map_err(Box::new) +} + +#[cfg(test)] +#[test] +fn root() { + let path = parse_path(".").unwrap(); + dbg!(path.get_path()); + assert_eq!(path.get_path(), Vec::new()) +} + +#[test] +fn body() { + let path = parse_path(".-b").unwrap(); + dbg!(path.get_path()); + assert_eq!(path.get_path(), vec![ParseNodes::Body]) +} + +#[test] +fn branch() { + let path = parse_path(".-f").unwrap(); + dbg!(path.get_path()); + assert_eq!(path.get_path(), vec![ParseNodes::If(false)]) +} + +#[test] +fn offset() { + let path = parse_path(".-0-1").unwrap(); + dbg!(path.get_path()); + assert_eq!( + path.get_path(), + vec![ParseNodes::Offset(0), ParseNodes::Offset(1)] + ) +} + +#[test] +fn multiple() { + let path = parse_path(".-0-1-b-t").unwrap(); + dbg!(path.get_path()); + assert_eq!( + path.get_path(), + vec![ + ParseNodes::Offset(0), + ParseNodes::Offset(1), + ParseNodes::Body, + ParseNodes::If(true) + ] + ) +} diff --git a/interp/src/debugger/debugging_context/context.rs b/interp/src/debugger/debugging_context/context.rs index 7f317d3aaa..6856f5ae05 100644 --- a/interp/src/debugger/debugging_context/context.rs +++ b/interp/src/debugger/debugging_context/context.rs @@ -648,10 +648,8 @@ impl DebuggingContext { let watchpoint_indicies = self.watchpoints.get_by_group(x).unwrap(); match watchpoint_indicies { - WatchPointIndices::Before(x) => return x.iter(), - WatchPointIndices::Both { before, .. } => { - return before.iter() - } + WatchPointIndices::Before(x) => x.iter(), + WatchPointIndices::Both { before, .. } => before.iter(), // this is stupid but works _ => [].iter(), } @@ -665,10 +663,8 @@ impl DebuggingContext { let watchpoint_indicies = self.watchpoints.get_by_group(x).unwrap(); match watchpoint_indicies { - WatchPointIndices::After(x) => return x.iter(), - WatchPointIndices::Both { after, .. } => { - return after.iter() - } + WatchPointIndices::After(x) => x.iter(), + WatchPointIndices::Both { after, .. } => after.iter(), // this is stupid but works _ => [].iter(), } diff --git a/interp/src/errors.rs b/interp/src/errors.rs index 43b1e2c775..192ef33443 100644 --- a/interp/src/errors.rs +++ b/interp/src/errors.rs @@ -225,7 +225,7 @@ pub enum RuntimeError { OverflowError, #[error(transparent)] - ConflictingAssignments(ConflictingAssignments), + ConflictingAssignments(Box), } // this is silly but needed to make the program print something sensible when returning @@ -299,7 +299,8 @@ impl RuntimeError { } match self { - RuntimeError::ConflictingAssignments(ConflictingAssignments { target, a1, a2 }) => { + RuntimeError::ConflictingAssignments(boxed_err) => { + let ConflictingAssignments { target, a1, a2 } = *boxed_err; let (a1_str, a1_source) = assign_to_string(&a1, env); let (a2_str, a2_source) = assign_to_string(&a2, env); @@ -323,8 +324,20 @@ impl RuntimeError { RuntimeError::UndefinedReadAddr(c) => CiderError::GenericError(format!("Attempted to read from an undefined memory address from memory named \"{}\"", env.get_full_name(c))), RuntimeError::ClockError(clk) => { match clk { - ClockError::ReadWrite(c) => CiderError::GenericError(format!("Concurrent read & write to the same register/memory {}", env.get_full_name(c).underline())), - ClockError::WriteWrite(c) => CiderError::GenericError(format!("Concurrent writes to the same register/memory {}", env.get_full_name(c).underline())), + ClockError::ReadWrite(c, num) => { + if let Some(entry_number) = num { + CiderError::GenericError(format!("Concurrent read & write to the same memory {} in slot {}", env.get_full_name(c).underline(), entry_number)) + } else { + CiderError::GenericError(format!("Concurrent read & write to the same register {}", env.get_full_name(c).underline())) + } + }, + ClockError::WriteWrite(c, num) => { + if let Some(entry_number) = num { + CiderError::GenericError(format!("Concurrent writes to the same memory {} in slot {}", env.get_full_name(c).underline(), entry_number)) + } else { + CiderError::GenericError(format!("Concurrent writes to the same register {}", env.get_full_name(c).underline())) + } + }, c => CiderError::GenericError(format!("Unexpected clock error: {c:?}")), } } diff --git a/interp/src/flatten/flat_ir/base.rs b/interp/src/flatten/flat_ir/base.rs index 93b8c100cc..e92d08fc45 100644 --- a/interp/src/flatten/flat_ir/base.rs +++ b/interp/src/flatten/flat_ir/base.rs @@ -13,6 +13,7 @@ use crate::{ serialization::PrintCode, }; use baa::{BitVecOps, BitVecValue}; +use std::collections::HashSet; // making these all u32 for now, can give the macro an optional type as the // second arg to contract or expand as needed @@ -428,12 +429,24 @@ impl From<(GlobalCellIdx, AssignmentIdx)> for AssignmentWinner { /// A struct representing a value that has been assigned to a port. It wraps a /// concrete value and the "winner" which assigned it. -#[derive(Clone, PartialEq)] +#[derive(Clone)] pub struct AssignedValue { val: BitVecValue, winner: AssignmentWinner, thread: Option, clocks: Option, + propagate_clocks: bool, + transitive_clocks: Option>, +} + +impl AssignedValue { + pub fn eq_no_transitive_clocks(&self, other: &Self) -> bool { + self.val == other.val + && self.winner == other.winner + && self.thread == other.thread + && self.clocks == other.clocks + && self.propagate_clocks == other.propagate_clocks + } } impl std::fmt::Debug for AssignedValue { @@ -442,6 +455,8 @@ impl std::fmt::Debug for AssignedValue { .field("val", &self.val.to_bit_str()) .field("winner", &self.winner) .field("thread", &self.thread) + .field("clocks", &self.clocks) + .field("propagate_clocks", &self.propagate_clocks) .finish() } } @@ -461,9 +476,37 @@ impl AssignedValue { winner: winner.into(), thread: None, clocks: None, + propagate_clocks: false, + transitive_clocks: None, } } + /// Adds a clock to the set of transitive reads associated with this value + pub fn add_transitive_clock(&mut self, clock_pair: ClockPair) { + self.transitive_clocks + .get_or_insert_with(Default::default) + .insert(clock_pair); + } + + pub fn add_transitive_clocks>( + &mut self, + clocks: I, + ) { + self.transitive_clocks + .get_or_insert_with(Default::default) + .extend(clocks); + } + + pub fn iter_transitive_clocks( + &self, + ) -> impl Iterator + '_ { + self.transitive_clocks + .as_ref() + .map(|set| set.iter().copied()) + .into_iter() + .flatten() + } + pub fn with_thread(mut self, thread: ThreadIdx) -> Self { self.thread = Some(thread); self @@ -479,6 +522,31 @@ impl AssignedValue { self } + pub fn with_clocks_optional( + mut self, + clock_pair: Option, + ) -> Self { + self.clocks = clock_pair; + self + } + + pub fn with_transitive_clocks_opt( + mut self, + clocks: Option>, + ) -> Self { + self.transitive_clocks = clocks; + self + } + + pub fn with_propagate_clocks(mut self) -> Self { + self.propagate_clocks = true; + self + } + + pub fn set_propagate_clocks(&mut self, propagate_clocks: bool) { + self.propagate_clocks = propagate_clocks; + } + /// Returns true if the two AssignedValues do not have the same winner pub fn has_conflict_with(&self, other: &Self) -> bool { self.winner != other.winner @@ -497,36 +565,21 @@ impl AssignedValue { /// A utility constructor which returns a new implicitly assigned value with /// a one bit high value pub fn implicit_bit_high() -> Self { - Self { - val: BitVecValue::tru(), - winner: AssignmentWinner::Implicit, - thread: None, - clocks: None, - } + Self::new(BitVecValue::tru(), AssignmentWinner::Implicit) } /// A utility constructor which returns an [`AssignedValue`] with the given /// value and a [`AssignmentWinner::Cell`] as the winner #[inline] pub fn cell_value(val: BitVecValue) -> Self { - Self { - val, - winner: AssignmentWinner::Cell, - thread: None, - clocks: None, - } + Self::new(val, AssignmentWinner::Cell) } /// A utility constructor which returns an [`AssignedValue`] with the given /// value and a [`AssignmentWinner::Implicit`] as the winner #[inline] pub fn implicit_value(val: BitVecValue) -> Self { - Self { - val, - winner: AssignmentWinner::Implicit, - thread: None, - clocks: None, - } + Self::new(val, AssignmentWinner::Implicit) } /// A utility constructor which returns an [`AssignedValue`] with a one bit @@ -549,6 +602,14 @@ impl AssignedValue { pub fn clocks(&self) -> Option<&ClockPair> { self.clocks.as_ref() } + + pub fn transitive_clocks(&self) -> Option<&HashSet> { + self.transitive_clocks.as_ref() + } + + pub fn propagate_clocks(&self) -> bool { + self.propagate_clocks + } } #[derive(Debug, Clone, Default)] @@ -595,6 +656,14 @@ impl PortValue { self } + pub fn transitive_clocks(&self) -> Option<&HashSet> { + self.0.as_ref().and_then(|x| x.transitive_clocks()) + } + + pub fn as_option_mut(&mut self) -> Option<&mut AssignedValue> { + self.0.as_mut() + } + /// If the value is defined, returns the value cast to a boolean. Otherwise /// returns `None`. It will panic if the given value is not one bit wide. pub fn as_bool(&self) -> Option { diff --git a/interp/src/flatten/flat_ir/control/translator.rs b/interp/src/flatten/flat_ir/control/translator.rs index 5efa5d284c..193af5bf13 100644 --- a/interp/src/flatten/flat_ir/control/translator.rs +++ b/interp/src/flatten/flat_ir/control/translator.rs @@ -290,8 +290,13 @@ fn translate_component( .collect_vec(); // Will need to rethink this at some point - if go_ports.len() != 1 || done_ports.len() != 1 { - todo!("handle multiple go and done ports"); + if go_ports.len() > 1 || done_ports.len() > 1 { + todo!( + "handle multiple go and done ports. On component: {}", + comp.name + ); + } else if comp.is_comb { + todo!("handle comb components. On component: {}", comp.name); } let go_port = &go_ports[0]; let done_port = &done_ports[0]; diff --git a/interp/src/flatten/flat_ir/flatten_trait.rs b/interp/src/flatten/flat_ir/flatten_trait.rs index 41d0cf0b33..4aa500c567 100644 --- a/interp/src/flatten/flat_ir/flatten_trait.rs +++ b/interp/src/flatten/flat_ir/flatten_trait.rs @@ -74,7 +74,7 @@ where handle: &'a mut VecHandle<'outer, In, Idx, Out>, } -impl<'a, 'outer, In, Idx, Out> SingleHandle<'a, 'outer, In, Idx, Out> +impl<'outer, In, Idx, Out> SingleHandle<'_, 'outer, In, Idx, Out> where Idx: IndexRef, { diff --git a/interp/src/flatten/primitives/combinational.rs b/interp/src/flatten/primitives/combinational.rs index 8df70e4352..3da8b99194 100644 --- a/interp/src/flatten/primitives/combinational.rs +++ b/interp/src/flatten/primitives/combinational.rs @@ -4,7 +4,10 @@ use crate::flatten::{ all_defined, comb_primitive, declare_ports, ports, prim_trait::UpdateStatus, utils::floored_division, Primitive, }, - structures::environment::PortMap, + structures::{ + environment::PortMap, + index_trait::{IndexRef, SplitIndexRange}, + }, }; use baa::{BitVecOps, BitVecValue}; @@ -34,18 +37,18 @@ impl Primitive for StdConst { }) } - fn exec_cycle(&mut self, _port_map: &mut PortMap) -> UpdateResult { - Ok(UpdateStatus::Unchanged) - } - - fn has_comb(&self) -> bool { + fn has_comb_path(&self) -> bool { true } - fn has_stateful(&self) -> bool { + fn has_stateful_path(&self) -> bool { false } + fn get_ports(&self) -> SplitIndexRange { + SplitIndexRange::new(self.out, self.out, (self.out.index() + 1).into()) + } + fn clone_boxed(&self) -> Box { Box::new(self.clone()) } @@ -53,19 +56,19 @@ impl Primitive for StdConst { #[derive(Clone)] pub struct StdMux { - base: GlobalPortIdx, + base_port: GlobalPortIdx, } impl StdMux { - declare_ports![ COND: 0, TRU: 1, FAL:2, OUT: 3]; + declare_ports![ COND: 0, TRU: 1 | FAL:2, OUT: 3 ]; pub fn new(base: GlobalPortIdx) -> Self { - Self { base } + Self { base_port: base } } } impl Primitive for StdMux { fn exec_comb(&self, port_map: &mut PortMap) -> UpdateResult { - ports![&self.base; cond: Self::COND, tru: Self::TRU, fal: Self::FAL, out: Self::OUT]; + ports![&self.base_port; cond: Self::COND, tru: Self::TRU, fal: Self::FAL, out: Self::OUT]; let winning_idx = port_map[cond].as_bool().map(|c| if c { tru } else { fal }); @@ -83,10 +86,14 @@ impl Primitive for StdMux { } } - fn has_stateful(&self) -> bool { + fn has_stateful_path(&self) -> bool { false } + fn get_ports(&self) -> SplitIndexRange { + self.get_signature() + } + fn clone_boxed(&self) -> Box { Box::new(self.clone()) } @@ -300,6 +307,10 @@ impl Primitive for StdUndef { Ok(UpdateStatus::Unchanged) } + fn get_ports(&self) -> SplitIndexRange { + SplitIndexRange::new(self.0, self.0, (self.0.index() + 1).into()) + } + fn clone_boxed(&self) -> Box { Box::new(self.clone()) } diff --git a/interp/src/flatten/primitives/macros.rs b/interp/src/flatten/primitives/macros.rs index bf75c6f1b4..21084c770e 100644 --- a/interp/src/flatten/primitives/macros.rs +++ b/interp/src/flatten/primitives/macros.rs @@ -1,12 +1,70 @@ +// taken from https://veykril.github.io/tlborm/decl-macros/building-blocks/counting.html +macro_rules! replace_expr { + ($_t:tt $sub:expr) => { + $sub + }; +} + +macro_rules! count_tts { + ($($tts:tt)*) => {0usize $(+ $crate::flatten::primitives::macros::replace_expr!($tts 1usize))*}; +} +// -- https://veykril.github.io/tlborm/decl-macros/building-blocks/counting.html + +pub(crate) use count_tts; +pub(crate) use replace_expr; + macro_rules! ports { ($base:expr; $( $port:ident : $offset:expr ),+ ) => { $(let $port: $crate::flatten::flat_ir::prelude::GlobalPortIdx = ($crate::flatten::structures::index_trait::IndexRef::index($base) + $offset).into();)+ } } +/// Declare a list of ports for the primitive with their offsets given relative +/// to the cell's base port.A vertical bar is used to separate the input and +/// output ports +/// +/// ## NOTE: These must be given in ascending order. And the base port of the struct must be named `base_port` +/// +/// ```ignore +/// // Example +/// // declare COND, TRU, FAL as input ports +/// // declare OUT as output port +/// declare_ports![ COND: 0, TRU: 1, FAL:2 | OUT: 3]; +/// ``` macro_rules! declare_ports { - ($( $port:ident : $offset:expr ),+ $(,)? ) => { + ($( $input_port:ident : $input_offset:literal ),+ $(,)? | $( $output_port:ident : $output_offset:literal ),+ $(,)? ) => { + + $( + #[allow(non_upper_case_globals)] + const $input_port: usize = $input_offset; // this is a usize because it encodes the position of the port! + )+ + + $( + #[allow(non_upper_case_globals)] + const $output_port: usize = $output_offset; // this is a usize because it encodes the position of the port! + )+ + // determine the offset of the first and last output ports + const SPLIT_IDX: usize = [$($output_offset),+][0]; + const END_IDX: usize = ([$($output_offset),+][$crate::flatten::primitives::macros::count_tts!($($output_offset)+) - 1]) + 1; + + #[inline] + pub fn get_signature(&self) -> $crate::flatten::structures::index_trait::SplitIndexRange<$crate::flatten::flat_ir::prelude::GlobalPortIdx> { + use $crate::flatten::structures::index_trait::IndexRef; + + $crate::flatten::structures::index_trait::SplitIndexRange::new(self.base_port, + $crate::flatten::flat_ir::prelude::GlobalPortIdx::new(self.base_port.index() + Self::SPLIT_IDX), + $crate::flatten::flat_ir::prelude::GlobalPortIdx::new(self.base_port.index() + Self::END_IDX), + ) + } + } +} + +/// Declare a list of ports for the primitive with their offsets given relative +/// to the cell's base port. Unlike `declare_ports` this does not generate a +/// `get_signature` function or distinguish between input and output ports. +macro_rules! declare_ports_no_signature { + ($( $port:ident : $offset:literal ),+ $(,)? ) => { $( #[allow(non_upper_case_globals)] const $port: usize = $offset; // this is a usize because it encodes the position of the port! @@ -27,6 +85,7 @@ macro_rules! make_getters { } pub(crate) use declare_ports; +pub(crate) use declare_ports_no_signature; pub(crate) use make_getters; pub(crate) use ports; @@ -46,8 +105,7 @@ macro_rules! comb_primitive { impl $name { - $crate::flatten::primitives::macros::declare_ports![$($port: $port_idx),+]; - $crate::flatten::primitives::macros::declare_ports![$out_port: $out_port_idx,]; + $crate::flatten::primitives::macros::declare_ports![$($port: $port_idx),+ | $out_port: $out_port_idx]; #[allow(non_snake_case)] pub fn new( @@ -98,10 +156,14 @@ macro_rules! comb_primitive { } } - fn has_stateful(&self) -> bool { + fn has_stateful_path(&self) -> bool { false } + fn get_ports(&self) -> $crate::flatten::structures::index_trait::SplitIndexRange<$crate::flatten::flat_ir::prelude::GlobalPortIdx> { + self.get_signature() + } + fn clone_boxed(&self) -> Box { Box::new(Self { base_port: self.base_port, diff --git a/interp/src/flatten/primitives/prim_trait.rs b/interp/src/flatten/primitives/prim_trait.rs index 199adce455..4a94324485 100644 --- a/interp/src/flatten/primitives/prim_trait.rs +++ b/interp/src/flatten/primitives/prim_trait.rs @@ -4,6 +4,7 @@ use crate::{ flat_ir::base::GlobalPortIdx, structures::{ environment::{clock::ClockMap, PortMap}, + index_trait::SplitIndexRange, thread::ThreadMap, }, }, @@ -109,15 +110,15 @@ pub trait Primitive { Ok(UpdateStatus::Unchanged) } - fn exec_cycle(&mut self, _port_map: &mut PortMap) -> UpdateResult { - Ok(UpdateStatus::Unchanged) + fn exec_cycle(&mut self, _port_map: &mut PortMap) -> RuntimeResult<()> { + Ok(()) } - fn has_comb(&self) -> bool { + fn has_comb_path(&self) -> bool { true } - fn has_stateful(&self) -> bool { + fn has_stateful_path(&self) -> bool { true } @@ -135,6 +136,13 @@ pub trait Primitive { None } + fn get_ports(&self) -> SplitIndexRange; + + /// Returns `true` if this primitive only has a combinational part + fn is_combinational(&self) -> bool { + self.has_comb_path() && !self.has_stateful_path() + } + fn clone_boxed(&self) -> Box; } @@ -153,7 +161,7 @@ pub trait RaceDetectionPrimitive: Primitive { port_map: &mut PortMap, _clock_map: &mut ClockMap, _thread_map: &ThreadMap, - ) -> UpdateResult { + ) -> RuntimeResult<()> { self.exec_cycle(port_map) } @@ -163,20 +171,3 @@ pub trait RaceDetectionPrimitive: Primitive { fn clone_boxed_rd(&self) -> Box; } - -/// An empty primitive implementation used for testing. It does not do anything -/// and has no ports of any kind -#[derive(Clone, Copy)] -pub struct DummyPrimitive; - -impl DummyPrimitive { - pub fn new_dyn() -> Box { - Box::new(Self) - } -} - -impl Primitive for DummyPrimitive { - fn clone_boxed(&self) -> Box { - Box::new(*self) - } -} diff --git a/interp/src/flatten/primitives/stateful/math.rs b/interp/src/flatten/primitives/stateful/math.rs index c8fb091e48..1d47fadf5d 100644 --- a/interp/src/flatten/primitives/stateful/math.rs +++ b/interp/src/flatten/primitives/stateful/math.rs @@ -1,11 +1,14 @@ -use crate::flatten::{ - flat_ir::prelude::*, - primitives::{ - declare_ports, ports, - prim_trait::*, - utils::{floored_division, int_sqrt, ShiftBuffer}, +use crate::{ + errors::RuntimeResult, + flatten::{ + flat_ir::prelude::*, + primitives::{ + declare_ports, ports, + prim_trait::*, + utils::{floored_division, int_sqrt, ShiftBuffer}, + }, + structures::{environment::PortMap, index_trait::SplitIndexRange}, }, - structures::environment::PortMap, }; use baa::{BitVecOps, BitVecValue, WidthInt}; use num_traits::Euclid; @@ -20,7 +23,7 @@ pub struct StdMultPipe { } impl StdMultPipe { - declare_ports![_CLK: 0, RESET: 1, GO: 2, LEFT: 3, RIGHT: 4, OUT: 5, DONE: 6]; + declare_ports![_CLK: 0, RESET: 1, GO: 2, LEFT: 3, RIGHT: 4, | OUT: 5, DONE: 6]; pub fn new(base_port: GlobalPortIdx, width: u32) -> Self { Self { base_port, @@ -55,7 +58,7 @@ impl Primitive for StdMultPipe { Ok(out_changed | done_signal) } - fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult { + fn exec_cycle(&mut self, port_map: &mut PortMap) -> RuntimeResult<()> { ports![&self.base_port; left: Self::LEFT, right: Self::RIGHT, @@ -95,7 +98,7 @@ impl Primitive for StdMultPipe { self.done_is_high = false; } - let done_signal = port_map.insert_val_general( + port_map.insert_val_general( done, AssignedValue::cell_value(if self.done_is_high { BitVecValue::tru() @@ -104,10 +107,13 @@ impl Primitive for StdMultPipe { }), )?; - Ok( - port_map.write_exact_unchecked(out, self.current_output.clone()) - | done_signal, - ) + port_map.write_exact_unchecked(out, self.current_output.clone()); + + Ok(()) + } + + fn get_ports(&self) -> SplitIndexRange { + self.get_signature() } } @@ -122,7 +128,7 @@ pub struct StdDivPipe { } impl StdDivPipe { - declare_ports![_CLK: 0, RESET: 1, GO: 2, LEFT: 3, RIGHT: 4, OUT_QUOTIENT: 5, OUT_REMAINDER: 6, DONE: 7]; + declare_ports![_CLK: 0, RESET: 1, GO: 2, LEFT: 3, RIGHT: 4, | OUT_QUOTIENT: 5, OUT_REMAINDER: 6, DONE: 7]; pub fn new(base_port: GlobalPortIdx, width: u32) -> Self { Self { base_port, @@ -158,7 +164,7 @@ impl Primitive Ok(quot_changed | rem_changed | done_signal) } - fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult { + fn exec_cycle(&mut self, port_map: &mut PortMap) -> RuntimeResult<()> { ports![&self.base_port; left: Self::LEFT, right: Self::RIGHT, @@ -226,13 +232,15 @@ impl Primitive self.done_is_high = false; } - let done_signal = port_map.set_done(done, self.done_is_high)?; - let quot_changed = port_map - .write_exact_unchecked(out_quot, self.output_quotient.clone()); - let rem_changed = port_map - .write_exact_unchecked(out_rem, self.output_remainder.clone()); + port_map.set_done(done, self.done_is_high)?; + port_map.write_exact_unchecked(out_quot, self.output_quotient.clone()); + port_map.write_exact_unchecked(out_rem, self.output_remainder.clone()); - Ok(quot_changed | rem_changed | done_signal) + Ok(()) + } + + fn get_ports(&self) -> SplitIndexRange { + self.get_signature() } } @@ -246,7 +254,7 @@ pub struct Sqrt { } impl Sqrt { - declare_ports!(_CLK: 0, RESET: 1, GO: 2, IN: 3, OUT: 4, DONE: 5); + declare_ports!(_CLK: 0, RESET: 1, GO: 2, IN: 3, | OUT: 4, DONE: 5); pub fn new( base_port: GlobalPortIdx, width: u32, @@ -277,7 +285,7 @@ impl Primitive for Sqrt { Ok(out_changed | done_changed) } - fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult { + fn exec_cycle(&mut self, port_map: &mut PortMap) -> RuntimeResult<()> { ports![&self.base_port; reset: Self::RESET, go: Self::GO, @@ -315,11 +323,14 @@ impl Primitive for Sqrt { self.done_is_high = false; } - let done_signal = port_map.set_done(done, self.done_is_high)?; - let out_changed = - port_map.write_exact_unchecked(out, self.output.clone()); + port_map.set_done(done, self.done_is_high)?; + port_map.write_exact_unchecked(out, self.output.clone()); - Ok(out_changed | done_signal) + Ok(()) + } + + fn get_ports(&self) -> SplitIndexRange { + self.get_signature() } } @@ -334,7 +345,7 @@ pub struct FxpMultPipe { } impl FxpMultPipe { - declare_ports![_CLK: 0, RESET: 1, GO: 2, LEFT: 3, RIGHT: 4, OUT: 5, DONE: 6]; + declare_ports![_CLK: 0, RESET: 1, GO: 2, LEFT: 3, RIGHT: 4, | OUT: 5, DONE: 6]; pub fn new( base_port: GlobalPortIdx, int_width: u32, @@ -376,7 +387,7 @@ impl Primitive for FxpMultPipe { Ok(out_changed | done_signal) } - fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult { + fn exec_cycle(&mut self, port_map: &mut PortMap) -> RuntimeResult<()> { ports![&self.base_port; left: Self::LEFT, right: Self::RIGHT, @@ -425,7 +436,7 @@ impl Primitive for FxpMultPipe { self.done_is_high = false; } - let done_signal = port_map.insert_val_general( + port_map.insert_val_general( done, AssignedValue::cell_value(if self.done_is_high { BitVecValue::tru() @@ -433,11 +444,13 @@ impl Primitive for FxpMultPipe { BitVecValue::fals() }), )?; + port_map.write_exact_unchecked(out, self.current_output.clone()); - Ok( - port_map.write_exact_unchecked(out, self.current_output.clone()) - | done_signal, - ) + Ok(()) + } + + fn get_ports(&self) -> SplitIndexRange { + self.get_signature() } } @@ -453,7 +466,7 @@ pub struct FxpDivPipe { } impl FxpDivPipe { - declare_ports![_CLK: 0, RESET: 1, GO: 2, LEFT: 3, RIGHT: 4, OUT_REMAINDER: 5, OUT_QUOTIENT: 6, DONE: 7]; + declare_ports![_CLK: 0, RESET: 1, GO: 2, LEFT: 3, RIGHT: 4, | OUT_REMAINDER: 5, OUT_QUOTIENT: 6, DONE: 7]; pub fn new( base_port: GlobalPortIdx, int_width: u32, @@ -500,7 +513,7 @@ impl Primitive Ok(quot_changed | rem_changed | done_signal) } - fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult { + fn exec_cycle(&mut self, port_map: &mut PortMap) -> RuntimeResult<()> { ports![&self.base_port; left: Self::LEFT, right: Self::RIGHT, @@ -570,12 +583,14 @@ impl Primitive self.done_is_high = false; } - let done_signal = port_map.set_done(done, self.done_is_high)?; - let quot_changed = port_map - .write_exact_unchecked(out_quot, self.output_quotient.clone()); - let rem_changed = port_map - .write_exact_unchecked(out_rem, self.output_remainder.clone()); + port_map.set_done(done, self.done_is_high)?; + port_map.write_exact_unchecked(out_quot, self.output_quotient.clone()); + port_map.write_exact_unchecked(out_rem, self.output_remainder.clone()); - Ok(quot_changed | rem_changed | done_signal) + Ok(()) + } + + fn get_ports(&self) -> SplitIndexRange { + self.get_signature() } } diff --git a/interp/src/flatten/primitives/stateful/memories.rs b/interp/src/flatten/primitives/stateful/memories.rs index 8365907f26..9ef3a1a51b 100644 --- a/interp/src/flatten/primitives/stateful/memories.rs +++ b/interp/src/flatten/primitives/stateful/memories.rs @@ -8,17 +8,17 @@ use crate::{ prelude::{AssignedValue, GlobalPortIdx, PortValue}, }, primitives::{ - declare_ports, make_getters, ports, + declare_ports, declare_ports_no_signature, make_getters, ports, prim_trait::{RaceDetectionPrimitive, UpdateResult, UpdateStatus}, utils::infer_thread_id, Primitive, }, structures::{ environment::{ - clock::{new_clock, ClockMap, ValueWithClock}, + clock::{new_clock_pair, ClockMap, ValueWithClock}, PortMap, }, - index_trait::IndexRef, + index_trait::{IndexRef, SplitIndexRange}, thread::{ThreadIdx, ThreadMap}, }, }, @@ -36,7 +36,7 @@ pub struct StdReg { } impl StdReg { - declare_ports![IN: 0, WRITE_EN: 1, _CLK: 2, RESET: 3, OUT: 4, DONE: 5]; + declare_ports![IN: 0, WRITE_EN: 1, _CLK: 2, RESET: 3, | OUT: 4, DONE: 5]; pub fn new( base_port: GlobalPortIdx, @@ -44,8 +44,10 @@ impl StdReg { width: u32, clocks: &mut Option<&mut ClockMap>, ) -> Self { - let internal_state = - ValueWithClock::zero(width, new_clock(clocks), new_clock(clocks)); + let internal_state = ValueWithClock::zero( + width, + new_clock_pair(clocks, global_idx, None), + ); Self { base_port, global_idx, @@ -60,7 +62,7 @@ impl Primitive for StdReg { Box::new(self.clone()) } - fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult { + fn exec_cycle(&mut self, port_map: &mut PortMap) -> RuntimeResult<()> { ports![&self.base_port; input: Self::IN, write_en: Self::WRITE_EN, @@ -69,7 +71,7 @@ impl Primitive for StdReg { done: Self::DONE ]; - let done_port = if port_map[reset].as_bool().unwrap_or_default() { + if port_map[reset].as_bool().unwrap_or_default() { self.internal_state.value = BitVecValue::zero(self.internal_state.value.width()); port_map.insert_val_general( @@ -97,12 +99,12 @@ impl Primitive for StdReg { )? }; - Ok(done_port - | port_map.insert_val_general( - out_idx, - AssignedValue::cell_value(self.internal_state.value.clone()) - .with_clocks(self.internal_state.clocks), - )?) + port_map.insert_val_general( + out_idx, + AssignedValue::cell_value(self.internal_state.value.clone()) + .with_clocks(self.internal_state.clocks), + )?; + Ok(()) } fn exec_comb(&self, port_map: &mut PortMap) -> UpdateResult { @@ -141,6 +143,10 @@ impl Primitive for StdReg { fn dump_memory_state(&self) -> Option> { Some(self.internal_state.value.clone().to_bytes_le()) } + + fn get_ports(&self) -> SplitIndexRange { + self.get_signature() + } } impl RaceDetectionPrimitive for StdReg { @@ -157,7 +163,7 @@ impl RaceDetectionPrimitive for StdReg { port_map: &mut PortMap, clock_map: &mut ClockMap, thread_map: &ThreadMap, - ) -> UpdateResult { + ) -> RuntimeResult<()> { ports![&self.base_port; input: Self::IN, write_en: Self::WRITE_EN, @@ -178,7 +184,7 @@ impl RaceDetectionPrimitive for StdReg { self.internal_state .clocks .check_write(current_clock_idx, clock_map) - .map_err(|e| e.add_cell_info(self.global_idx))?; + .map_err(|e| e.add_cell_info(self.global_idx, None))?; } self.exec_cycle(port_map) @@ -200,7 +206,7 @@ impl MemDx { } } - declare_ports![ + declare_ports_no_signature![ SEQ_ADDR0: 2, COMB_ADDR0: 0, SEQ_ADDR1: 3, COMB_ADDR1: 1, SEQ_ADDR2: 4, COMB_ADDR2: 2, @@ -412,7 +418,7 @@ pub struct CombMem { global_idx: GlobalCellIdx, } impl CombMem { - declare_ports![ + declare_ports_no_signature![ WRITE_DATA:0, WRITE_EN: 1, _CLK: 2, @@ -442,11 +448,10 @@ impl CombMem { { let shape = size.into(); let mut internal_state = Vec::with_capacity(shape.size()); - for _ in 0..shape.size() { + for i in 0_usize..shape.size() { internal_state.push(ValueWithClock::zero( width, - new_clock(clocks), - new_clock(clocks), + new_clock_pair(clocks, global_idx, Some(i.try_into().unwrap())), )); } @@ -479,8 +484,16 @@ impl CombMem { let internal_state = data .chunks_exact(byte_count as usize) .map(|x| BitVecValue::from_bytes_le(x, width)) - .map(|x| { - ValueWithClock::new(x, new_clock(clocks), new_clock(clocks)) + .enumerate() + .map(|(i, x)| { + ValueWithClock::new( + x, + new_clock_pair( + clocks, + global_idx, + Some(i.try_into().unwrap()), + ), + ) }) .collect_vec(); @@ -561,7 +574,7 @@ impl Primitive for CombMem { Ok(done_signal | read) } - fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult { + fn exec_cycle(&mut self, port_map: &mut PortMap) -> RuntimeResult<()> { // These two behave like false when undefined let reset = port_map[self.reset_port()].as_bool().unwrap_or_default(); let write_en = port_map[self.write_en()].as_bool().unwrap_or_default(); @@ -573,7 +586,7 @@ impl Primitive for CombMem { )?; let (read_data, done) = (self.read_data(), self.done()); - let done = if write_en && !reset { + if write_en && !reset { let addr = addr.ok_or(RuntimeError::UndefinedWriteAddr(self.global_idx))?; @@ -589,17 +602,17 @@ impl Primitive for CombMem { }; if let Some(addr) = addr { - Ok(port_map.insert_val_general( + port_map.insert_val_general( read_data, AssignedValue::cell_value( self.internal_state[addr].value.clone(), ) .with_clocks(self.internal_state[addr].clocks), - )? | done) + )?; } else { port_map.write_undef(read_data)?; - Ok(done) } + Ok(()) } fn serialize(&self, code: Option) -> Serializable { @@ -621,6 +634,14 @@ impl Primitive for CombMem { fn dump_memory_state(&self) -> Option> { Some(self.dump_data()) } + + fn get_ports(&self) -> SplitIndexRange { + SplitIndexRange::new( + self.base_port, + self.read_data(), + (self.done().index() + 1).into(), + ) + } } impl RaceDetectionPrimitive for CombMem { @@ -661,7 +682,7 @@ impl RaceDetectionPrimitive for CombMem { port_map: &mut PortMap, clock_map: &mut ClockMap, thread_map: &ThreadMap, - ) -> UpdateResult { + ) -> RuntimeResult<()> { let thread = self.infer_thread(port_map); if let Some(addr) = self.addresser.calculate_addr( port_map, @@ -677,7 +698,12 @@ impl RaceDetectionPrimitive for CombMem { if port_map[self.write_en()].as_bool().unwrap_or_default() { val.clocks .check_write(thread_clock, clock_map) - .map_err(|e| e.add_cell_info(self.global_idx))?; + .map_err(|e| { + e.add_cell_info( + self.global_idx, + Some(addr.try_into().unwrap()), + ) + })?; } } else if addr != 0 || port_map[self.write_en()].as_bool().unwrap_or_default() @@ -718,11 +744,10 @@ impl SeqMem { ) -> Self { let shape = size.into(); let mut internal_state = Vec::with_capacity(shape.size()); - for _ in 0..shape.size() { + for i in 0_usize..shape.size() { internal_state.push(ValueWithClock::zero( width, - new_clock(clocks), - new_clock(clocks), + new_clock_pair(clocks, global_idx, Some(i.try_into().unwrap())), )); } @@ -756,8 +781,16 @@ impl SeqMem { let internal_state = data .chunks_exact(byte_count as usize) .map(|x| BitVecValue::from_bytes_le(x, width)) - .map(|x| { - ValueWithClock::new(x, new_clock(clocks), new_clock(clocks)) + .enumerate() + .map(|(i, x)| { + ValueWithClock::new( + x, + new_clock_pair( + clocks, + global_idx, + Some(i.try_into().unwrap()), + ), + ) }) .collect_vec(); @@ -779,14 +812,14 @@ impl SeqMem { } } - declare_ports![ + declare_ports_no_signature![ _CLK: 0, RESET: 1, ]; // these port offsets are placed after the address ports and so need the end // of the address base to work correctly. - declare_ports![ + declare_ports_no_signature![ CONTENT_ENABLE: 0, WRITE_ENABLE: 1, WRITE_DATA: 2, @@ -860,7 +893,7 @@ impl Primitive for SeqMem { Ok(done_signal | out_signal) } - fn exec_cycle(&mut self, port_map: &mut PortMap) -> UpdateResult { + fn exec_cycle(&mut self, port_map: &mut PortMap) -> RuntimeResult<()> { let reset = port_map[self.reset()].as_bool().unwrap_or_default(); let write_en = port_map[self.write_enable()].as_bool().unwrap_or_default(); @@ -896,7 +929,7 @@ impl Primitive for SeqMem { self.done_is_high = false; } - let done_changed = port_map.insert_val_general( + port_map.insert_val_general( self.done(), AssignedValue::cell_value(if self.done_is_high { BitVecValue::tru() @@ -904,16 +937,15 @@ impl Primitive for SeqMem { BitVecValue::fals() }), )?; - Ok(done_changed - | port_map - .write_exact_unchecked(self.read_data(), self.read_out.clone())) + port_map.write_exact_unchecked(self.read_data(), self.read_out.clone()); + Ok(()) } - fn has_comb(&self) -> bool { + fn has_comb_path(&self) -> bool { false } - fn has_stateful(&self) -> bool { + fn has_stateful_path(&self) -> bool { true } @@ -936,6 +968,14 @@ impl Primitive for SeqMem { fn dump_memory_state(&self) -> Option> { Some(self.dump_data()) } + + fn get_ports(&self) -> SplitIndexRange { + SplitIndexRange::new( + self.base_port, + self.read_data(), + (self.done().index() + 1).into(), + ) + } } impl RaceDetectionPrimitive for SeqMem { @@ -961,7 +1001,7 @@ impl RaceDetectionPrimitive for SeqMem { port_map: &mut PortMap, clock_map: &mut ClockMap, thread_map: &ThreadMap, - ) -> UpdateResult { + ) -> RuntimeResult<()> { let thread = self.infer_thread(port_map); if let Some(addr) = self.addresser.calculate_addr( port_map, @@ -986,7 +1026,12 @@ impl RaceDetectionPrimitive for SeqMem { ), clock_map, ) - .map_err(|e| e.add_cell_info(self.global_idx))?; + .map_err(|e| { + e.add_cell_info( + self.global_idx, + Some(addr.try_into().unwrap()), + ) + })?; } else if port_map[self.content_enable()] .as_bool() .unwrap_or_default() @@ -1001,7 +1046,12 @@ impl RaceDetectionPrimitive for SeqMem { ), clock_map, ) - .map_err(|e| e.add_cell_info(self.global_idx))?; + .map_err(|e| { + e.add_cell_info( + self.global_idx, + Some(addr.try_into().unwrap()), + ) + })?; } } } diff --git a/interp/src/flatten/structures/environment/assignments.rs b/interp/src/flatten/structures/environment/assignments.rs index 1fdb672111..2306404b1e 100644 --- a/interp/src/flatten/structures/environment/assignments.rs +++ b/interp/src/flatten/structures/environment/assignments.rs @@ -1,6 +1,6 @@ use crate::flatten::{ flat_ir::prelude::{GlobalCellIdx, LocalPortOffset}, - structures::thread::ThreadIdx, + structures::thread::{ThreadIdx, ThreadMap}, }; use super::env::AssignmentRange; @@ -11,6 +11,43 @@ pub struct GroupInterfacePorts { pub done: LocalPortOffset, } +/// An enum describing the source of a set of assignments +#[derive(Debug, Clone, Copy)] +pub enum AssignType { + /// Assignments come from a comb group + Combinational, + /// Assignments are continuous + Continuous, + /// Assignments come from a group or invoke + Control, +} + +impl AssignType { + /// Returns `true` if the assign source is [`Combinational`]. + /// + /// [`Combinational`]: AssignType::Combinational + #[must_use] + pub fn is_combinational(&self) -> bool { + matches!(self, Self::Combinational) + } + + /// Returns `true` if the assign source is [`Continuous`]. + /// + /// [`Continuous`]: AssignType::Continuous + #[must_use] + pub fn is_continuous(&self) -> bool { + matches!(self, Self::Continuous) + } + + /// Returns `true` if the assign type is [`Control`]. + /// + /// [`Control`]: AssignType::Control + #[must_use] + pub fn is_control(&self) -> bool { + matches!(self, Self::Control) + } +} + /// A group of assignments that is scheduled to be evaluated #[derive(Debug)] pub struct ScheduledAssignments { @@ -18,23 +55,49 @@ pub struct ScheduledAssignments { pub assignments: AssignmentRange, pub interface_ports: Option, pub thread: Option, - pub is_cont: bool, + pub assign_type: AssignType, } impl ScheduledAssignments { - pub fn new( + pub fn new_control( active_cell: GlobalCellIdx, assignments: AssignmentRange, interface_ports: Option, thread: Option, - is_comb: bool, ) -> Self { Self { active_cell, assignments, interface_ports, thread, - is_cont: is_comb, + assign_type: AssignType::Control, + } + } + + pub fn new_combinational( + active_cell: GlobalCellIdx, + assignments: AssignmentRange, + ) -> Self { + Self { + active_cell, + assignments, + interface_ports: None, + thread: None, + assign_type: AssignType::Combinational, + } + } + + pub fn new_continuous( + active_cell: GlobalCellIdx, + assignments: AssignmentRange, + ) -> Self { + Self { + active_cell, + assignments, + interface_ports: None, + // all continuous assignments are executed under a single control thread + thread: Some(ThreadMap::continuous_thread()), + assign_type: AssignType::Continuous, } } } diff --git a/interp/src/flatten/structures/environment/clock.rs b/interp/src/flatten/structures/environment/clock.rs index 9d30eabc24..1ba27735da 100644 --- a/interp/src/flatten/structures/environment/clock.rs +++ b/interp/src/flatten/structures/environment/clock.rs @@ -3,18 +3,18 @@ use std::{ collections::HashMap, hash::Hash, num::NonZeroU32, + ops::{Index, IndexMut}, }; use crate::flatten::{ flat_ir::base::GlobalCellIdx, structures::{ - index_trait::{impl_index_nonzero, IndexRef}, - indexed_map::IndexedMap, + index_trait::impl_index_nonzero, indexed_map::IndexedMap, thread::ThreadIdx, }, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ClockIdx(NonZeroU32); impl_index_nonzero!(ClockIdx); @@ -22,18 +22,74 @@ use baa::BitVecValue; use itertools::Itertools; use thiserror::Error; -pub type ClockMap = IndexedMap>; pub type ThreadClockPair = (ThreadIdx, ClockIdx); +#[derive(Debug, Clone)] +pub struct ClockPairInfo { + /// The cell that this clock pair was generated for + pub attached_cell: GlobalCellIdx, + /// An optional entry number within the given cell. This is used for + /// memories but not for registers + pub entry_number: Option, +} + +#[derive(Debug, Default, Clone)] +pub struct ClockMap { + clocks: IndexedMap>, + reverse_map: HashMap, +} + impl ClockMap { + pub fn new() -> Self { + Self::default() + } + /// pushes a new clock into the map and returns its index pub fn new_clock(&mut self) -> ClockIdx { - self.push(VectorClock::new()) + self.clocks.push(VectorClock::new()) + } + + pub fn new_clock_pair(&mut self) -> ClockPair { + let read = self.new_clock(); + let write = self.new_clock(); + ClockPair::new(read, write) + } + + pub fn insert_reverse_entry( + &mut self, + pair: ClockPair, + cell: GlobalCellIdx, + entry_number: Option, + ) { + self.reverse_map.insert( + pair, + ClockPairInfo { + attached_cell: cell, + entry_number, + }, + ); + } + + pub fn lookup_cell(&self, pair: ClockPair) -> Option<&ClockPairInfo> { + self.reverse_map.get(&pair) } /// Returns a new clock that is the clone of the given clock pub fn fork_clock(&mut self, parent: ClockIdx) -> ClockIdx { - self.push(self[parent].clone()) + self.clocks.push(self.clocks[parent].clone()) + } +} + +impl Index for ClockMap { + type Output = VectorClock; + fn index(&self, index: ClockIdx) -> &Self::Output { + &self.clocks[index] + } +} + +impl IndexMut for ClockMap { + fn index_mut(&mut self, index: ClockIdx) -> &mut Self::Output { + &mut self.clocks[index] } } @@ -86,11 +142,18 @@ impl Counter for u128 { /// If the clock map is provided, use it to create a new clock. Otherwise, /// return the 0th clock idx. -pub fn new_clock(clock_map: &mut Option<&mut ClockMap>) -> ClockIdx { - clock_map - .as_mut() - .map(|c| c.new_clock()) - .unwrap_or(ClockIdx::new(0)) +pub fn new_clock_pair( + clock_map: &mut Option<&mut ClockMap>, + cell: GlobalCellIdx, + entry_number: Option, +) -> ClockPair { + if let Some(map) = clock_map { + let pair = map.new_clock_pair(); + map.insert_reverse_entry(pair, cell, entry_number); + pair + } else { + ClockPair::zero() + } } /// A simple vector clock implementation. @@ -294,38 +357,40 @@ pub struct ValueWithClock { } impl ValueWithClock { - pub fn zero( - width: u32, - reading_clock: ClockIdx, - writing_clock: ClockIdx, - ) -> Self { + pub fn zero(width: u32, clocks: ClockPair) -> Self { Self { value: BitVecValue::zero(width), - clocks: ClockPair::new(reading_clock, writing_clock), + clocks, } } - pub fn new( - value: BitVecValue, - write_clock: ClockIdx, - read_clock: ClockIdx, - ) -> Self { + pub fn new(value: BitVecValue, clock_pair: ClockPair) -> Self { Self { value, - clocks: ClockPair::new(read_clock, write_clock), + clocks: clock_pair, } } } /// A struct containing the read and write clocks for a value. This is small /// enough to be copied around easily -#[derive(Debug, Clone, PartialEq, Copy)] +#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)] pub struct ClockPair { pub read_clock: ClockIdx, pub write_clock: ClockIdx, } impl ClockPair { + /// Returns a new clock pair where both indices point to the zero clock. + /// This should only be used as a placeholder entry for when clocks are not + /// actually being tracked. + pub fn zero() -> Self { + Self { + read_clock: ClockIdx::from(0), + write_clock: ClockIdx::from(0), + } + } + pub fn new(read_clock: ClockIdx, write_clock: ClockIdx) -> Self { Self { read_clock, @@ -356,6 +421,18 @@ impl ClockPair { } } + /// A wrapper method which checks the read and adds cell info on an error + pub fn check_read_w_cell( + &self, + (thread, reading_clock): ThreadClockPair, + clock_map: &mut ClockMap, + cell: GlobalCellIdx, + entry_number: Option, + ) -> Result<(), ClockError> { + self.check_read((thread, reading_clock), clock_map) + .map_err(|e| e.add_cell_info(cell, entry_number)) + } + pub fn check_write( &self, writing_clock: ClockIdx, @@ -371,6 +448,7 @@ impl ClockPair { .partial_cmp(&clock_map[self.read_clock]) .is_none() { + // dbg!(&clock_map[writing_clock], &clock_map[self.read_clock]); Err(ClockError::ReadWriteUnhelpful) } else if clock_map[writing_clock] .partial_cmp(&clock_map[self.write_clock]) @@ -394,16 +472,24 @@ pub enum ClockError { #[error("Concurrent writes to the same register/memory")] WriteWriteUnhelpful, #[error("Concurrent read & write to the same register/memory {0:?}")] - ReadWrite(GlobalCellIdx), + ReadWrite(GlobalCellIdx, Option), #[error("Concurrent writes to the same register/memory {0:?}")] - WriteWrite(GlobalCellIdx), + WriteWrite(GlobalCellIdx, Option), } impl ClockError { - pub fn add_cell_info(self, cell: GlobalCellIdx) -> Self { + pub fn add_cell_info( + self, + cell: GlobalCellIdx, + entry_number: Option, + ) -> Self { match self { - ClockError::ReadWriteUnhelpful => ClockError::ReadWrite(cell), - ClockError::WriteWriteUnhelpful => ClockError::WriteWrite(cell), + ClockError::ReadWriteUnhelpful => { + ClockError::ReadWrite(cell, entry_number) + } + ClockError::WriteWriteUnhelpful => { + ClockError::WriteWrite(cell, entry_number) + } _ => self, } } diff --git a/interp/src/flatten/structures/environment/env.rs b/interp/src/flatten/structures/environment/env.rs index 7b7360a57b..d8f3f13925 100644 --- a/interp/src/flatten/structures/environment/env.rs +++ b/interp/src/flatten/structures/environment/env.rs @@ -3,13 +3,17 @@ use super::{ context::Context, index_trait::IndexRange, indexed_map::IndexedMap, }, assignments::{GroupInterfacePorts, ScheduledAssignments}, - clock::{ClockMap, VectorClock}, - program_counter::{ControlTuple, PcMaps, ProgramCounter, WithEntry}, + clock::ClockMap, + program_counter::{ + ControlTuple, ParEntry, PcMaps, ProgramCounter, WithEntry, + }, traverser::{Path, TraversalError}, }; use crate::{ configuration::{LoggingConfig, RuntimeConfig}, - errors::{BoxedCiderError, CiderResult, ConflictingAssignments}, + errors::{ + BoxedCiderError, BoxedRuntimeError, CiderResult, ConflictingAssignments, + }, flatten::{ flat_ir::{ base::{ @@ -49,8 +53,8 @@ use itertools::Itertools; use owo_colors::OwoColorize; use slog::{info, warn, Logger}; -use std::fmt::Debug; use std::fmt::Write; +use std::{convert::Into, fmt::Debug}; pub type PortMap = IndexedMap; @@ -74,7 +78,11 @@ impl PortMap { val: PortValue, ) -> UpdateStatus { if self[target].is_undef() && val.is_undef() - || self[target].as_option() == val.as_option() + || self[target] + .as_option() + .zip(val.as_option()) + .map(|(a, b)| a.eq_no_transitive_clocks(b)) + .unwrap_or_default() { UpdateStatus::Unchanged } else { @@ -94,10 +102,12 @@ impl PortMap { &mut self, target: GlobalPortIdx, val: AssignedValue, - ) -> Result { + ) -> Result> { match self[target].as_option() { // unchanged - Some(t) if *t == val => Ok(UpdateStatus::Unchanged), + Some(t) if t.eq_no_transitive_clocks(&val) => { + Ok(UpdateStatus::Unchanged) + } // conflict // TODO: Fix to make the error more helpful Some(t) @@ -110,7 +120,8 @@ impl PortMap { target, a1: t.clone(), a2: val, - }) + } + .into()) } // changed Some(_) | None => { @@ -429,8 +440,9 @@ impl + Clone> Environment { let root = ctx.as_ref().entry_point; let aux = &ctx.as_ref().secondary[root]; - let mut clocks = IndexedMap::new(); - let root_clock = clocks.push(VectorClock::new()); + let mut clocks = ClockMap::new(); + let root_clock = clocks.new_clock(); + let continuous_clock = clocks.new_clock(); let mut env = Self { ports: PortMap::with_capacity(aux.port_offset_map.count()), @@ -443,7 +455,7 @@ impl + Clone> Environment { ), pc: ProgramCounter::new_empty(), clocks, - thread_map: ThreadMap::new(root_clock), + thread_map: ThreadMap::new(root_clock, continuous_clock), ctx, memory_header: None, pinned_ports: PinnedPorts::new(), @@ -462,6 +474,7 @@ impl + Clone> Environment { let root_thread = ThreadMap::root_thread(); env.clocks[root_clock].increment(&root_thread); + env.clocks[continuous_clock].increment(&ThreadMap::continuous_thread()); // Initialize program counter // TODO griffin: Maybe refactor into a separate function @@ -572,6 +585,24 @@ impl + Clone> Environment { self.control_ports.insert(done, 1); } + // ref cells and ports are initialized to None + for (ref_cell, def_idx) in comp_aux.ref_cell_offset_map.iter() { + let info = &self.ctx.as_ref().secondary[*def_idx]; + + for port_idx in info.ports.iter() { + let port_actual = self.ref_ports.push(None); + debug_assert_eq!( + &self.cells[comp].as_comp().unwrap().index_bases + port_idx, + port_actual + ); + } + let cell_actual = self.ref_cells.push(None); + debug_assert_eq!( + &self.cells[comp].as_comp().unwrap().index_bases + ref_cell, + cell_actual + ) + } + for (cell_off, def_idx) in comp_aux.cell_offset_map.iter() { let info = &self.ctx.as_ref().secondary[*def_idx]; if !info.prototype.is_component() { @@ -634,23 +665,6 @@ impl + Clone> Environment { } } } - - // ref cells and ports are initialized to None - for (ref_cell, def_idx) in comp_aux.ref_cell_offset_map.iter() { - let info = &self.ctx.as_ref().secondary[*def_idx]; - for port_idx in info.ports.iter() { - let port_actual = self.ref_ports.push(None); - debug_assert_eq!( - &self.cells[comp].as_comp().unwrap().index_bases + port_idx, - port_actual - ) - } - let cell_actual = self.ref_cells.push(None); - debug_assert_eq!( - &self.cells[comp].as_comp().unwrap().index_bases + ref_cell, - cell_actual - ) - } } pub fn get_comp_go(&self, comp: GlobalCellIdx) -> GlobalPortIdx { @@ -827,10 +841,17 @@ impl + Clone> Environment { let node = &ctx.primary[point.control_node_idx]; match node { ControlNode::Enable(x) => { + let go = &self.cells[point.comp].unwrap_comp().index_bases + + self.ctx().primary[x.group()].go; println!( - "{}::{}", + "{}::{}{}", self.get_full_name(point.comp), - ctx.lookup_name(x.group()).underline() + ctx.lookup_name(x.group()).underline(), + if self.ports[go].as_bool().unwrap_or_default() { + "" + } else { + " [done]" + } ); } ControlNode::Invoke(x) => { @@ -889,7 +910,7 @@ impl + Clone> Environment { /// Attempt to find the parent cell for a port. If no such cell exists (i.e. /// it is a hole port, then it returns None) - fn get_parent_cell_from_port( + fn _get_parent_cell_from_port( &self, port: PortRef, comp: GlobalCellIdx, @@ -1320,6 +1341,17 @@ impl + Clone> Environment { } } +enum ControlNodeEval { + Reprocess, + Stop { retain_node: bool }, +} + +impl ControlNodeEval { + fn stop(retain_node: bool) -> Self { + ControlNodeEval::Stop { retain_node } + } +} + /// The core functionality of a simulator. Clonable. #[derive(Clone)] pub struct BaseSimulator + Clone> { @@ -1633,7 +1665,7 @@ impl + Clone> BaseSimulator { ControlNode::Enable(e) => { let group = &self.ctx().primary[e.group()]; - Some(ScheduledAssignments::new( + Some(ScheduledAssignments::new_control( node.comp, group.assignments, Some(GroupInterfacePorts { @@ -1641,17 +1673,17 @@ impl + Clone> BaseSimulator { done: group.done, }), *thread, - false, )) } - ControlNode::Invoke(i) => Some(ScheduledAssignments::new( - node.comp, - i.assignments, - None, - *thread, - false, - )), + ControlNode::Invoke(i) => { + Some(ScheduledAssignments::new_control( + node.comp, + i.assignments, + None, + *thread, + )) + } ControlNode::Empty(_) => None, // non-leaf nodes @@ -1663,25 +1695,20 @@ impl + Clone> BaseSimulator { } }) .chain(self.env.pc.continuous_assigns().iter().map(|x| { - ScheduledAssignments::new(x.comp, x.assigns, None, None, true) + ScheduledAssignments::new_continuous(x.comp, x.assigns) })) .chain(self.env.pc.with_map().iter().map( |(ctrl_pt, with_entry)| { - let assigns = - self.ctx().primary[with_entry.group].assignments; - ScheduledAssignments::new( + ScheduledAssignments::new_combinational( ctrl_pt.comp, - assigns, - None, - with_entry.thread, - false, + self.ctx().primary[with_entry.group].assignments, ) }, )) .collect() } - /// A helper function which inserts indicies for the ref cells and ports + /// A helper function which inserts indices for the ref cells and ports /// used in the invoke statement fn initialize_ref_cells( &mut self, @@ -1851,7 +1878,7 @@ impl + Clone> BaseSimulator { { with_map.insert( node.clone(), - WithEntry::new(invoke.comb_group.unwrap(), thread), + WithEntry::new(invoke.comb_group.unwrap()), ); } @@ -1877,7 +1904,7 @@ impl + Clone> BaseSimulator { { with_map.insert( node.clone(), - WithEntry::new(i.cond_group().unwrap(), thread), + WithEntry::new(i.cond_group().unwrap()), ); } } @@ -1886,7 +1913,7 @@ impl + Clone> BaseSimulator { { with_map.insert( node.clone(), - WithEntry::new(w.cond_group().unwrap(), thread), + WithEntry::new(w.cond_group().unwrap()), ); } } @@ -1911,34 +1938,71 @@ impl + Clone> BaseSimulator { pub fn step(&mut self) -> CiderResult<()> { self.converge()?; - let out: Result<(), BoxedCiderError> = { - let mut result = Ok(()); - for cell in self.env.cells.values_mut() { - match cell { - CellLedger::Primitive { cell_dyn } => { - let res = cell_dyn.exec_cycle(&mut self.env.ports); - if res.is_err() { - result = Err(res.unwrap_err()); - break; + if self.conf.check_data_race { + let mut clock_map = std::mem::take(&mut self.env.clocks); + for cell in self.env.cells.values() { + if !matches!(&cell, CellLedger::Component(_)) { + let dyn_prim = match cell { + CellLedger::Primitive { cell_dyn } => &**cell_dyn, + CellLedger::RaceDetectionPrimitive { cell_dyn } => { + cell_dyn.as_primitive() } - } + CellLedger::Component(_) => { + unreachable!() + } + }; - CellLedger::RaceDetectionPrimitive { cell_dyn } => { - let res = cell_dyn.exec_cycle_checked( - &mut self.env.ports, - &mut self.env.clocks, - &self.env.thread_map, - ); - if res.is_err() { - result = Err(res.unwrap_err()); - break; + if !dyn_prim.is_combinational() { + let sig = dyn_prim.get_ports(); + for port in sig.iter_first() { + if let Some(val) = self.env.ports[port].as_option() + { + if val.propagate_clocks() + && (val.transitive_clocks().is_some()) + { + // For non-combinational cells with + // transitive reads, we will check them at + // the cycle boundary and attribute the read + // to the continuous thread + self.check_read( + ThreadMap::continuous_thread(), + port, + &mut clock_map, + ) + .map_err(|e| { + e.prettify_message(&self.env) + })? + } + } } } - CellLedger::Component(_) => {} } } - result.map_err(|e| e.prettify_message(&self.env).into()) - }; + + self.env.clocks = clock_map; + } + + let out: Result<(), BoxedRuntimeError> = self + .env + .cells + .values_mut() + .filter_map(|cell| match cell { + CellLedger::Primitive { cell_dyn } => { + Some(cell_dyn.exec_cycle(&mut self.env.ports)) + } + + CellLedger::RaceDetectionPrimitive { cell_dyn } => { + Some(cell_dyn.exec_cycle_checked( + &mut self.env.ports, + &mut self.env.clocks, + &self.env.thread_map, + )) + } + CellLedger::Component(_) => None, + }) + .collect(); + + out.map_err(|e| e.prettify_message(&self.env))?; self.env.pc.clear_finished_comps(); @@ -1948,7 +2012,10 @@ impl + Clone> BaseSimulator { let mut removed = vec![]; - for (i, node) in vecs.iter_mut().enumerate() { + let mut i = 0; + + while i < vecs.len() { + let node = &mut vecs[i]; let keep_node = self .evaluate_control_node( node, @@ -1956,8 +2023,16 @@ impl + Clone> BaseSimulator { (&mut par_map, &mut with_map, &mut repeat_map), ) .map_err(|e| e.prettify_message(&self.env))?; - if !keep_node { - removed.push(i); + match keep_node { + ControlNodeEval::Reprocess => { + continue; + } + ControlNodeEval::Stop { retain_node } => { + if !retain_node { + removed.push(i); + } + i += 1; + } } } @@ -1972,7 +2047,7 @@ impl + Clone> BaseSimulator { // insert all the new nodes from the par into the program counter self.env.pc.vec_mut().extend(new_nodes); - out + Ok(()) } fn evaluate_control_node( @@ -1980,7 +2055,7 @@ impl + Clone> BaseSimulator { node: &mut ControlTuple, new_nodes: &mut Vec, maps: PcMaps, - ) -> RuntimeResult { + ) -> RuntimeResult { let (node_thread, node) = node; let (par_map, with_map, repeat_map) = maps; let comp_go = self.env.get_comp_go(node.comp); @@ -1999,173 +2074,41 @@ impl + Clone> BaseSimulator { { // if the go port is low or the done port is high, we skip the // node without doing anything - return Ok(true); + return Ok(ControlNodeEval::stop(true)); } // just considering a single node case for the moment let retain_bool = match &ctx.primary[node.control_node_idx] { - ControlNode::Seq(seq) => { - if !seq.is_empty() { - let next = seq.stms()[0]; - *node = node.new_retain_comp(next); - true - } else { - node.mutate_into_next(self.env.ctx.as_ref()) - } - } - ControlNode::Par(par) => { - if par_map.contains_key(node) { - let count = par_map.get_mut(node).unwrap(); - *count -= 1; - - if *count == 0 { - par_map.remove(node); - if self.conf.check_data_race { - let thread = - thread.expect("par nodes should have a thread"); - - let child_clock_idx = - self.env.thread_map.unwrap_clock_id(thread); - let parent = - self.env.thread_map[thread].parent().unwrap(); - let parent_clock = - self.env.thread_map.unwrap_clock_id(parent); - let child_clock = std::mem::take( - &mut self.env.clocks[child_clock_idx], - ); - self.env.clocks[parent_clock].sync(&child_clock); - self.env.clocks[child_clock_idx] = child_clock; - assert!(self.env.thread_map[thread] - .parent() - .is_some()); - *node_thread = Some(parent); - self.env.clocks[parent_clock].increment(&parent); - } - node.mutate_into_next(self.env.ctx.as_ref()) - } else { - false - } - } else { - par_map.insert( - node.clone(), - par.stms().len().try_into().expect( - "More than (2^16 - 1 threads) in a par block. Are you sure this is a good idea?", - ), - ); - new_nodes.extend(par.stms().iter().map(|x| { - let thread = if self.conf.check_data_race { - let thread = - thread.expect("par nodes should have a thread"); - - let new_thread_idx: ThreadIdx = *(self - .env - .pc - .lookup_thread(node.comp, thread, *x) - .or_insert_with(|| { - let new_clock_idx = - self.env.clocks.new_clock(); - - self.env - .thread_map - .spawn(thread, new_clock_idx) - })); - - let new_clock_idx = self - .env - .thread_map - .unwrap_clock_id(new_thread_idx); - - self.env.clocks[new_clock_idx] = self.env.clocks - [self.env.thread_map.unwrap_clock_id(thread)] - .clone(); - - self.env.clocks[new_clock_idx] - .increment(&new_thread_idx); - - Some(new_thread_idx) - } else { - None - }; - - (thread, node.new_retain_comp(*x)) - })); - - if self.conf.check_data_race { - let thread = - thread.expect("par nodes should have a thread"); - let clock = self.env.thread_map.unwrap_clock_id(thread); - self.env.clocks[clock].increment(&thread); - } - - false - } - } + ControlNode::Seq(seq) => self.handle_seq(seq, node), + ControlNode::Par(par) => self.handle_par( + par_map, + node, + thread, + node_thread, + par, + new_nodes, + ), ControlNode::If(i) => self.handle_if(with_map, node, thread, i)?, ControlNode::While(w) => { self.handle_while(w, with_map, node, thread)? } ControlNode::Repeat(rep) => { - if let Some(count) = repeat_map.get_mut(node) { - *count -= 1; - if *count == 0 { - repeat_map.remove(node); - node.mutate_into_next(self.env.ctx.as_ref()) - } else { - *node = node.new_retain_comp(rep.body); - true - } - } else { - repeat_map.insert(node.clone(), rep.num_repeats); - *node = node.new_retain_comp(rep.body); - true - } + self.handle_repeat(repeat_map, node, rep) } // ===== leaf nodes ===== ControlNode::Empty(_) => { node.mutate_into_next(self.env.ctx.as_ref()) } - ControlNode::Enable(e) => { - let done_local = self.env.ctx.as_ref().primary[e.group()].done; - let done_idx = - &self.env.cells[node.comp].as_comp().unwrap().index_bases - + done_local; - if !self.env.ports[done_idx].as_bool().unwrap_or_default() { - true - } else { - // This group has finished running and may be removed - // this is somewhat dubious at the moment since it - // relies on the fact that the group done port will - // still be high since convergence hasn't propagated the - // low done signal yet. - node.mutate_into_next(self.env.ctx.as_ref()) - } - } - ControlNode::Invoke(i) => { - let done = self.get_global_port_idx(&i.done, node.comp); - - if i.comb_group.is_some() && !with_map.contains_key(node) { - with_map.insert( - node.clone(), - WithEntry::new(i.comb_group.unwrap(), thread), - ); - } - - if !self.env.ports[done].as_bool().unwrap_or_default() { - true - } else { - self.cleanup_ref_cells(node.comp, i); - - if i.comb_group.is_some() { - with_map.remove(node); - } - - node.mutate_into_next(self.env.ctx.as_ref()) - } - } + ControlNode::Enable(e) => self.handle_enable(e, node), + ControlNode::Invoke(i) => self.handle_invoke(i, node, with_map)?, }; + if retain_bool && node.should_reprocess(ctx) { + return Ok(ControlNodeEval::Reprocess); + } + if !retain_bool && ControlPoint::get_next(node, self.env.ctx.as_ref()).is_none() && // either we are not a par node, or we are the last par node (!matches!(&self.env.ctx.as_ref().primary[node.control_node_idx], ControlNode::Par(_)) || !par_map.contains_key(node)) @@ -2184,9 +2127,185 @@ impl + Clone> BaseSimulator { .control .unwrap(), ); - Ok(true) + Ok(ControlNodeEval::stop(true)) + } else { + Ok(ControlNodeEval::stop(retain_bool)) + } + } + + fn handle_seq(&mut self, seq: &Seq, node: &mut ControlPoint) -> bool { + if !seq.is_empty() { + let next = seq.stms()[0]; + *node = node.new_retain_comp(next); + true + } else { + node.mutate_into_next(self.env.ctx.as_ref()) + } + } + + fn handle_repeat( + &mut self, + repeat_map: &mut HashMap, + node: &mut ControlPoint, + rep: &Repeat, + ) -> bool { + if let Some(count) = repeat_map.get_mut(node) { + *count -= 1; + if *count == 0 { + repeat_map.remove(node); + node.mutate_into_next(self.env.ctx.as_ref()) + } else { + *node = node.new_retain_comp(rep.body); + true + } + } else { + repeat_map.insert(node.clone(), rep.num_repeats); + *node = node.new_retain_comp(rep.body); + true + } + } + + fn handle_enable(&mut self, e: &Enable, node: &mut ControlPoint) -> bool { + let done_local = self.env.ctx.as_ref().primary[e.group()].done; + let done_idx = + &self.env.cells[node.comp].as_comp().unwrap().index_bases + + done_local; + + if !self.env.ports[done_idx].as_bool().unwrap_or_default() { + true } else { - Ok(retain_bool) + // This group has finished running and may be removed + // this is somewhat dubious at the moment since it + // relies on the fact that the group done port will + // still be high since convergence hasn't propagated the + // low done signal yet. + node.mutate_into_next(self.env.ctx.as_ref()) + } + } + + fn handle_invoke( + &mut self, + i: &Invoke, + node: &mut ControlPoint, + with_map: &mut HashMap, + ) -> Result { + let done = self.get_global_port_idx(&i.done, node.comp); + if i.comb_group.is_some() && !with_map.contains_key(node) { + with_map + .insert(node.clone(), WithEntry::new(i.comb_group.unwrap())); + } + Ok(if !self.env.ports[done].as_bool().unwrap_or_default() { + true + } else { + self.cleanup_ref_cells(node.comp, i); + + if i.comb_group.is_some() { + with_map.remove(node); + } + + node.mutate_into_next(self.env.ctx.as_ref()) + }) + } + + fn handle_par( + &mut self, + par_map: &mut HashMap, + node: &mut ControlPoint, + thread: Option, + node_thread: &mut Option, + par: &Par, + new_nodes: &mut Vec<(Option, ControlPoint)>, + ) -> bool { + if par_map.contains_key(node) { + let par_entry = par_map.get_mut(node).unwrap(); + *par_entry.child_count_mut() -= 1; + + if self.conf.check_data_race { + par_entry.add_finished_thread( + thread.expect("par nodes should have a thread"), + ); + } + + if par_entry.child_count() == 0 { + let par_entry = par_map.remove(node).unwrap(); + if self.conf.check_data_race { + assert!(par_entry + .iter_finished_threads() + .map(|thread| { + self.env.thread_map[thread].parent().unwrap() + }) + .all_equal()); + let parent = + self.env.thread_map[thread.unwrap()].parent().unwrap(); + let parent_clock = + self.env.thread_map.unwrap_clock_id(parent); + + for child_thread in par_entry.iter_finished_threads() { + let child_clock_idx = + self.env.thread_map.unwrap_clock_id(child_thread); + + let child_clock = std::mem::take( + &mut self.env.clocks[child_clock_idx], + ); + + self.env.clocks[parent_clock].sync(&child_clock); + + self.env.clocks[child_clock_idx] = child_clock; + } + + *node_thread = Some(parent); + self.env.clocks[parent_clock].increment(&parent); + } + node.mutate_into_next(self.env.ctx.as_ref()) + } else { + false + } + } else { + par_map.insert( + node.clone(), + par.stms().len().try_into().expect( + "More than (2^16 - 1 threads) in a par block. Are you sure this is a good idea?", + ), + ); + new_nodes.extend(par.stms().iter().map(|x| { + let thread = if self.conf.check_data_race { + let thread = + thread.expect("par nodes should have a thread"); + + let new_thread_idx: ThreadIdx = *(self + .env + .pc + .lookup_thread(node.comp, thread, *x) + .or_insert_with(|| { + let new_clock_idx = self.env.clocks.new_clock(); + + self.env.thread_map.spawn(thread, new_clock_idx) + })); + + let new_clock_idx = + self.env.thread_map.unwrap_clock_id(new_thread_idx); + + self.env.clocks[new_clock_idx] = self.env.clocks + [self.env.thread_map.unwrap_clock_id(thread)] + .clone(); + + self.env.clocks[new_clock_idx].increment(&new_thread_idx); + + Some(new_thread_idx) + } else { + None + }; + + (thread, node.new_retain_comp(*x)) + })); + + if self.conf.check_data_race { + let thread = thread.expect("par nodes should have a thread"); + let clock = self.env.thread_map.unwrap_clock_id(thread); + self.env.clocks[clock].increment(&thread); + } + + false } } @@ -2207,27 +2326,20 @@ impl + Clone> BaseSimulator { GlobalPortRef::Ref(r) => self.env.ref_ports[r] .expect("While condition (ref) is undefined"), }; + if self.conf.check_data_race { - if let Some(clocks) = self.env.ports[idx].clocks() { - let read_clock = - self.env.thread_map.unwrap_clock_id(thread.unwrap()); - clocks - .check_read( - (thread.unwrap(), read_clock), - &mut self.env.clocks, - ) - .map_err(|e| { - e.add_cell_info( - self.env - .get_parent_cell_from_port( - w.cond_port(), - node.comp, - ) - .unwrap(), - ) - })?; - } + let mut clock_map = std::mem::take(&mut self.env.clocks); + + self.check_read_relative( + thread.unwrap(), + w.cond_port(), + node.comp, + &mut clock_map, + )?; + + self.env.clocks = clock_map; } + let result = self.env.ports[idx] .as_bool() .expect("While condition is undefined"); @@ -2271,25 +2383,11 @@ impl + Clone> BaseSimulator { }; if self.conf.check_data_race { - if let Some(clocks) = self.env.ports[idx].clocks() { - let read_clock = - self.env.thread_map.unwrap_clock_id(thread.unwrap()); - clocks - .check_read( - (thread.unwrap(), read_clock), - &mut self.env.clocks, - ) - .map_err(|e| { - e.add_cell_info( - self.env - .get_parent_cell_from_port( - i.cond_port(), - node.comp, - ) - .unwrap(), - ) - })?; - } + let mut clock_map = std::mem::take(&mut self.env.clocks); + + self.check_read(thread.unwrap(), idx, &mut clock_map)?; + + self.env.clocks = clock_map; } let result = self.env.ports[idx] @@ -2401,7 +2499,7 @@ impl + Clone> BaseSimulator { assignments, interface_ports, thread, - is_cont, + assign_type, } in assigns_bundle.iter() { let ledger = self.env.cells[*active_cell].as_comp().unwrap(); @@ -2429,7 +2527,7 @@ impl + Clone> BaseSimulator { .unwrap_or_else(|| { // if there is no go signal, then we want to run the // continuous assignments but not comb group assignments - if *is_cont { + if assign_type.is_continuous() { true } else { self.env.ports[comp_go] @@ -2453,55 +2551,14 @@ impl + Clone> BaseSimulator { let val = &self.env.ports[port]; if self.conf.debug_logging { - info!( - self.env.logger, - "Assignment fired in {}: {}\n wrote {}", - self.env.get_full_name(active_cell), - self.ctx() - .printer() - .print_assignment( - ledger.comp_id, - assign_idx - ) - .yellow(), - val.bold() + self.log_assignment( + active_cell, + ledger, + assign_idx, + val, ); } - if self.conf.check_data_race { - if let Some(clocks) = val.clocks() { - // skip checking clocks for continuous assignments - if !is_cont { - if let Some(thread) = thread { - let thread_clock = self - .env - .thread_map - .unwrap_clock_id(thread); - - clocks - .check_read( - (thread, thread_clock), - &mut self.env.clocks, - ) - .map_err(|e| { - // TODO griffin: find a less hacky way to - // do this - e.add_cell_info( - self.env - .get_parent_cell_from_port( - assign.src, - *active_cell, - ) - .unwrap(), - ) - })?; - } else { - panic!("cannot determine thread for non-continuous assignment that touches a checked port"); - } - } - } - } - let dest = self .get_global_port_idx(&assign.dst, *active_cell); @@ -2518,13 +2575,36 @@ impl + Clone> BaseSimulator { } if let Some(v) = val.as_option() { + let mut assigned_value = AssignedValue::new( + v.val().clone(), + (assign_idx, *active_cell), + ); + + // if this assignment is in a combinational + // context we want to propagate any clocks which + // are present. Since clocks aren't present + // when not running with `check_data_race`, this + // won't happen when the flag is not set + if (val.clocks().is_some() + || val.transitive_clocks().is_some()) + && (assign_type.is_combinational() + || assign_type.is_continuous()) + { + assigned_value = assigned_value + .with_transitive_clocks_opt( + val.transitive_clocks().cloned(), + ) + .with_propagate_clocks(); + // direct clock becomes a transitive clock + // on assignment + if let Some(c) = val.clocks() { + assigned_value.add_transitive_clock(c); + } + } + let result = self.env.ports.insert_val( dest, - AssignedValue::new( - v.val().clone(), - (assign_idx, *active_cell), - ) - .with_thread_optional(thread), + assigned_value.with_thread_optional(thread), ); let changed = match result { @@ -2566,79 +2646,15 @@ impl + Clone> BaseSimulator { todo!("Raise an error here since this assignment is undefining things: {}. Port currently has value: {}", self.env.ctx.as_ref().printer().print_assignment(ledger.comp_id, assign_idx), &self.env.ports[dest]) } } - - if self.conf.check_data_race { - if let Some(read_ports) = self - .env - .ctx - .as_ref() - .primary - .guard_read_map - .get(assign.guard) - { - for port in read_ports { - let port_idx = self.get_global_port_idx( - port, - *active_cell, - ); - if let Some(clocks) = - self.env.ports[port_idx].clocks() - { - let thread = thread - .expect("cannot determine thread"); - let thread_clock = self - .env - .thread_map - .unwrap_clock_id(thread); - clocks - .check_read( - (thread, thread_clock), - &mut self.env.clocks, - ) - .map_err(|e| { - // TODO griffin: find a less hacky way to - // do this - e.add_cell_info( - self.env - .get_parent_cell_from_port( - *port, - *active_cell, - ) - .unwrap(), - ) - })?; - } - } - } - } } } } - // Run all the primitives - let changed: bool = self - .env - .cells - .range() - .iter() - .filter_map(|x| match &mut self.env.cells[x] { - CellLedger::Primitive { cell_dyn } => { - Some(cell_dyn.exec_comb(&mut self.env.ports)) - } - CellLedger::RaceDetectionPrimitive { cell_dyn } => { - Some(cell_dyn.exec_comb_checked( - &mut self.env.ports, - &mut self.env.clocks, - &self.env.thread_map, - )) - } + if self.conf.check_data_race { + self.propagate_comb_reads(assigns_bundle)?; + } - CellLedger::Component(_) => None, - }) - .fold_ok(UpdateStatus::Unchanged, |has_changed, update| { - has_changed | update - })? - .as_bool(); + let changed = self.run_primitive_comb_path()?; has_changed |= changed; @@ -2666,70 +2682,394 @@ impl + Clone> BaseSimulator { } } + // check reads needs to happen before zeroing the go ports. If this is + // not observed then some read checks will be accidentally skipped + if self.conf.check_data_race { + self.handle_reads(assigns_bundle)?; + } + if self.conf.undef_guard_check { - let mut error_v = vec![]; - for bundle in assigns_bundle.iter() { - let ledger = - self.env.cells[bundle.active_cell].as_comp().unwrap(); - let go = bundle - .interface_ports - .as_ref() - .map(|x| &ledger.index_bases + x.go); - let done = bundle - .interface_ports - .as_ref() - .map(|x| &ledger.index_bases + x.done); + self.check_undefined_guards(assigns_bundle)?; + } + + // This should be the last update that occurs during convergence + self.zero_done_groups_go(assigns_bundle); - if !done - .and_then(|done| self.env.ports[done].as_bool()) + if self.conf.debug_logging { + info!(self.env.logger, "Finished combinational convergence"); + } + + Ok(()) + } + + /// For all groups in the given assignments, set the go port to zero if the + /// done port is high + fn zero_done_groups_go(&mut self, assigns_bundle: &[ScheduledAssignments]) { + for ScheduledAssignments { + active_cell, + interface_ports, + .. + } in assigns_bundle.iter() + { + if let Some(interface_ports) = interface_ports { + let ledger = self.env.cells[*active_cell].as_comp().unwrap(); + let go = &ledger.index_bases + interface_ports.go; + let done = &ledger.index_bases + interface_ports.done; + if self.env.ports[done].as_bool().unwrap_or_default() { + self.env.ports[go] = + PortValue::new_implicit(BitVecValue::zero(1)); + } + } + } + } + + /// A final pass meant to be run after convergence which does the following: + /// + /// 1. For successful assignments, check reads from the source port if applicable + /// 2. For non-continuous/combinational contexts, check all reads performed + /// by the guard regardless of whether the assignment fired or not + /// 3. For continuous/combinational contexts, update the transitive reads of + /// the value in the destination with the reads done by the guard, + /// regardless of success + fn handle_reads( + &mut self, + assigns_bundle: &[ScheduledAssignments], + ) -> Result<(), BoxedRuntimeError> { + // needed for mutability reasons + let mut clock_map = std::mem::take(&mut self.env.clocks); + + for ScheduledAssignments { + active_cell, + assignments, + interface_ports, + thread, + assign_type, + } in assigns_bundle.iter() + { + let ledger = self.env.cells[*active_cell].as_comp().unwrap(); + let go = + interface_ports.as_ref().map(|x| &ledger.index_bases + x.go); + + let comp_go = self.env.get_comp_go(*active_cell); + + let thread = self.compute_thread(comp_go, thread, go); + + // check for direct reads + if assign_type.is_control() + && go + .as_ref() + .map(|g| { + self.env.ports[*g].as_bool().unwrap_or_default() + && self.env.ports[comp_go] + .as_bool() + .unwrap_or_default() + }) .unwrap_or_default() - && go - .and_then(|go| self.env.ports[go].as_bool()) - .unwrap_or(true) - { - for assign in bundle.assignments.iter() { - let guard_idx = self.ctx().primary[assign].guard; - if self - .evaluate_guard(guard_idx, bundle.active_cell) - .is_none() - { - let inner_v = self - .ctx() - .primary - .guard_read_map - .get(guard_idx) + { + for assign_idx in assignments.iter() { + let assign = &self.env.ctx.as_ref().primary[assign_idx]; + + // read source + if self + .evaluate_guard(assign.guard, *active_cell) + .unwrap_or_default() + { + self.check_read_relative( + thread.unwrap(), + assign.src, + *active_cell, + &mut clock_map, + )?; + } + + // guard reads, assignment firing does not matter + if let Some(read_ports) = self + .env + .ctx + .as_ref() + .primary + .guard_read_map + .get(assign.guard) + { + for port in read_ports { + self.check_read_relative( + thread.unwrap(), + *port, + *active_cell, + &mut clock_map, + )?; + } + } + } + } + } + self.env.clocks = clock_map; + + Ok(()) + } + + /// For continuous/combinational contexts, update the transitive reads of + /// the value in the destination with the reads done by the guard, + /// regardless of success + fn propagate_comb_reads( + &mut self, + assigns_bundle: &[ScheduledAssignments], + ) -> Result<(), BoxedRuntimeError> { + for ScheduledAssignments { + active_cell, + assignments, + assign_type, + .. + } in assigns_bundle.iter() + { + let comp_go = self.env.get_comp_go(*active_cell); + + if (assign_type.is_combinational() + && self.env.ports[comp_go].as_bool().unwrap_or_default()) + || assign_type.is_continuous() + { + for assign_idx in assignments.iter() { + let assign = &self.env.ctx.as_ref().primary[assign_idx]; + let dest = + self.get_global_port_idx(&assign.dst, *active_cell); + + if let Some(read_ports) = self + .env + .ctx + .as_ref() + .primary + .guard_read_map + .get(assign.guard) + { + if self.env.ports[dest].is_def() { + let mut set_extension = HashSet::new(); + + for port in read_ports { + let port = self + .get_global_port_idx(port, *active_cell); + if let Some(clock) = + self.env.ports[port].clocks() + { + set_extension.insert(clock); + } + if let Some(clocks) = + self.env.ports[port].transitive_clocks() + { + set_extension + .extend(clocks.iter().copied()); + } + } + + self.env.ports[dest] + .as_option_mut() .unwrap() - .iter() - .filter_map(|p| { - let p = self.get_global_port_idx( - p, - bundle.active_cell, - ); - if self.env.ports[p].is_undef() { - Some(p) - } else { - None - } - }) - .collect_vec(); + .add_transitive_clocks(set_extension); - error_v.push((bundle.active_cell, assign, inner_v)) + // this is necessary for ports which were implicitly + // assigned zero and is redundant for other ports + // which will already have propagate_clocks set + self.env.ports[dest] + .as_option_mut() + .unwrap() + .set_propagate_clocks(true); } } } } - if !error_v.is_empty() { - return Err(RuntimeError::UndefinedGuardError(error_v).into()); + } + + Ok(()) + } + + /// Check for undefined guards and raise an error if any are found + fn check_undefined_guards( + &mut self, + assigns_bundle: &[ScheduledAssignments], + ) -> Result<(), BoxedRuntimeError> { + let mut error_v = vec![]; + for bundle in assigns_bundle.iter() { + let ledger = self.env.cells[bundle.active_cell].as_comp().unwrap(); + let go = bundle + .interface_ports + .as_ref() + .map(|x| &ledger.index_bases + x.go); + let done = bundle + .interface_ports + .as_ref() + .map(|x| &ledger.index_bases + x.done); + + if !done + .and_then(|done| self.env.ports[done].as_bool()) + .unwrap_or_default() + && go + .and_then(|go| self.env.ports[go].as_bool()) + .unwrap_or(true) + { + for assign in bundle.assignments.iter() { + let guard_idx = self.ctx().primary[assign].guard; + if self + .evaluate_guard(guard_idx, bundle.active_cell) + .is_none() + { + let inner_v = self + .ctx() + .primary + .guard_read_map + .get(guard_idx) + .unwrap() + .iter() + .filter_map(|p| { + let p = self + .get_global_port_idx(p, bundle.active_cell); + if self.env.ports[p].is_undef() { + Some(p) + } else { + None + } + }) + .collect_vec(); + + error_v.push((bundle.active_cell, assign, inner_v)) + } + } } } + if !error_v.is_empty() { + Err(RuntimeError::UndefinedGuardError(error_v).into()) + } else { + Ok(()) + } + } - if self.conf.debug_logging { - info!(self.env.logger, "Finished combinational convergence"); + fn run_primitive_comb_path(&mut self) -> Result { + let changed: bool = self + .env + .cells + .values_mut() + .filter_map(|x| match x { + CellLedger::Primitive { cell_dyn } => { + let result = cell_dyn.exec_comb(&mut self.env.ports); + + if result.is_ok() + && self.conf.check_data_race + && cell_dyn.is_combinational() + { + let mut working_set = vec![]; + let signature = cell_dyn.get_ports(); + + for port in signature.iter_first() { + let val = &self.env.ports[port]; + if let Some(val) = val.as_option() { + if val.propagate_clocks() + && (val.clocks().is_some() + || val.transitive_clocks().is_some()) + { + if let Some(clocks) = val.clocks() { + working_set.push(*clocks); + } + working_set + .extend(val.iter_transitive_clocks()); + } + } + } + + if signature.iter_second().len() == 1 { + let port = signature.iter_second().next().unwrap(); + let val = &mut self.env.ports[port]; + if let Some(val) = val.as_option_mut() { + val.add_transitive_clocks(working_set); + } + } else { + todo!("comb primitive with multiple outputs") + } + } + Some(result) + } + CellLedger::RaceDetectionPrimitive { cell_dyn } => { + Some(cell_dyn.exec_comb_checked( + &mut self.env.ports, + &mut self.env.clocks, + &self.env.thread_map, + )) + } + + CellLedger::Component(_) => None, + }) + .fold_ok(UpdateStatus::Unchanged, |has_changed, update| { + has_changed | update + })? + .as_bool(); + Ok(changed) + } + + /// A wrapper function for [check_read] which takes in a [PortRef] and the + /// active component cell and calls [check_read] with the appropriate [GlobalPortIdx] + fn check_read_relative( + &self, + thread: ThreadIdx, + port: PortRef, + active_cell: GlobalCellIdx, + clock_map: &mut ClockMap, + ) -> Result<(), BoxedRuntimeError> { + let global_port = self.get_global_port_idx(&port, active_cell); + self.check_read(thread, global_port, clock_map) + } + + fn check_read( + &self, + thread: ThreadIdx, + global_port: GlobalPortIdx, + clock_map: &mut ClockMap, + ) -> Result<(), BoxedRuntimeError> { + let val = &self.env.ports[global_port]; + let thread_clock = self.env.thread_map.unwrap_clock_id(thread); + + if val.clocks().is_some() && val.transitive_clocks().is_some() { + // TODO griffin: Sort this out + panic!("Value has both direct clock and transitive clock. This shouldn't happen?") + } else if let Some(clocks) = val.clocks() { + let info = clock_map.lookup_cell(clocks).expect("Clock pair without cell. This should never happen, please report this bug"); + clocks.check_read_w_cell( + (thread, thread_clock), + clock_map, + info.attached_cell, + info.entry_number, + )? + } else if let Some(transitive_clocks) = val.transitive_clocks() { + for clock_pair in transitive_clocks { + let info = clock_map.lookup_cell(*clock_pair).expect("Clock pair without cell. This should never happen, please report this bug"); + + clock_pair.check_read_w_cell( + (thread, thread_clock), + clock_map, + info.attached_cell, + info.entry_number, + )? + } } Ok(()) } + fn log_assignment( + &self, + active_cell: &GlobalCellIdx, + ledger: &ComponentLedger, + assign_idx: AssignmentIdx, + val: &PortValue, + ) { + info!( + self.env.logger, + "Assignment fired in {}: {}\n wrote {}", + self.env.get_full_name(active_cell), + self.ctx() + .printer() + .print_assignment(ledger.comp_id, assign_idx) + .yellow(), + val.bold() + ); + } + /// Attempts to compute the thread id for the given group/component. /// /// If the given thread is `None`, then the thread id is computed from the diff --git a/interp/src/flatten/structures/environment/program_counter.rs b/interp/src/flatten/structures/environment/program_counter.rs index 918303141e..698a4a0dab 100644 --- a/interp/src/flatten/structures/environment/program_counter.rs +++ b/interp/src/flatten/structures/environment/program_counter.rs @@ -1,6 +1,7 @@ use std::{collections::hash_map::Entry, num::NonZeroU32}; use ahash::{HashMap, HashMapExt}; +use smallvec::SmallVec; use super::super::context::Context; use crate::flatten::{ @@ -60,6 +61,19 @@ impl ControlPoint { } } + pub(super) fn should_reprocess(&self, ctx: &Context) -> bool { + match &ctx.primary.control[self.control_node_idx] { + ControlNode::Repeat(_) + | ControlNode::Empty(_) + | ControlNode::Seq(_) + | ControlNode::Par(_) => true, + ControlNode::Enable(_) + | ControlNode::If(_) + | ControlNode::While(_) + | ControlNode::Invoke(_) => false, + } + } + /// Returns a string showing the path from the root node to input node. This /// path is displayed in the minimal metadata path syntax. pub fn string_path(&self, ctx: &Context) -> String { @@ -417,14 +431,12 @@ pub struct WithEntry { pub group: CombGroupIdx, /// Whether or not a body has been executed. Only used by if statements pub entered: bool, - pub thread: Option, } impl WithEntry { - pub fn new(group: CombGroupIdx, thread: Option) -> Self { + pub fn new(group: CombGroupIdx) -> Self { Self { group, - thread, entered: false, } } @@ -434,12 +446,48 @@ impl WithEntry { } } +#[derive(Debug, Clone)] +pub struct ParEntry { + child_count: ChildCount, + finished_threads: SmallVec<[ThreadIdx; 4]>, +} + +impl ParEntry { + pub fn child_count_mut(&mut self) -> &mut ChildCount { + &mut self.child_count + } + + pub fn child_count(&self) -> u16 { + self.child_count + } + pub fn add_finished_thread(&mut self, thread: ThreadIdx) { + self.finished_threads.push(thread); + } + + pub fn iter_finished_threads( + &self, + ) -> impl Iterator + '_ { + self.finished_threads.iter().copied() + } +} + +impl TryFrom for ParEntry { + type Error = std::num::TryFromIntError; + + fn try_from(value: usize) -> Result { + Ok(ParEntry { + child_count: value.try_into()?, + finished_threads: SmallVec::new(), + }) + } +} + /// The program counter for the whole program execution. Wraps over a vector of /// the active leaf statements for each component instance. #[derive(Debug, Default, Clone)] pub(crate) struct ProgramCounter { vec: Vec, - par_map: HashMap, + par_map: HashMap, continuous_assigns: Vec, with_map: HashMap, repeat_map: HashMap, @@ -452,13 +500,13 @@ pub type ControlTuple = (Option, ControlPoint); pub type PcFields = ( Vec, - HashMap, + HashMap, HashMap, HashMap, ); pub type PcMaps<'a> = ( - &'a mut HashMap, + &'a mut HashMap, &'a mut HashMap, &'a mut HashMap, ); @@ -488,11 +536,11 @@ impl ProgramCounter { &mut self.vec } - pub fn _par_map_mut(&mut self) -> &mut HashMap { + pub fn _par_map_mut(&mut self) -> &mut HashMap { &mut self.par_map } - pub fn _par_map(&self) -> &HashMap { + pub fn _par_map(&self) -> &HashMap { &self.par_map } diff --git a/interp/src/flatten/structures/index_trait.rs b/interp/src/flatten/structures/index_trait.rs index 09f2588538..816f4c9dc0 100644 --- a/interp/src/flatten/structures/index_trait.rs +++ b/interp/src/flatten/structures/index_trait.rs @@ -164,6 +164,46 @@ where } } +/// A continuous range of indices that is split into two parts. +/// +/// Represents the ranges +/// `[start, split)` and `[split, end)` +#[derive(Debug, Clone)] +pub struct SplitIndexRange { + start: I, + split: I, + end: I, +} + +impl SplitIndexRange { + /// Create a new split index range. + /// + /// The `start` must be less than or equal to + /// the `split`, and the `split` must be less than or equal to the `end`. It will + /// panic if these conditions are not met. + pub fn new(start: I, split: I, end: I) -> Self { + assert!(start <= split); + assert!(split <= end); + + Self { start, split, end } + } + + /// Returns an iterator over the first segment of the range, i.e `[start, split)`. + pub fn iter_first(&self) -> OwnedIndexRangeIterator { + OwnedIndexRangeIterator::new(IndexRange::new(self.start, self.split)) + } + + /// Returns an iterator over the second segment of the range, i.e `[split, end)`. + pub fn iter_second(&self) -> OwnedIndexRangeIterator { + OwnedIndexRangeIterator::new(IndexRange::new(self.split, self.end)) + } + + /// Returns an iterator over the entire range. + pub fn iter_all(&self) -> OwnedIndexRangeIterator { + OwnedIndexRangeIterator::new(IndexRange::new(self.start, self.end)) + } +} + impl IntoIterator for IndexRange where I: IndexRef + PartialOrd, @@ -199,7 +239,7 @@ where current: I, } -impl<'a, I> ExactSizeIterator for IndexRangeIterator<'a, I> where +impl ExactSizeIterator for IndexRangeIterator<'_, I> where I: IndexRef + PartialOrd { } @@ -216,7 +256,7 @@ where } } -impl<'a, I> Iterator for IndexRangeIterator<'a, I> +impl Iterator for IndexRangeIterator<'_, I> where I: IndexRef + PartialOrd, { @@ -242,10 +282,10 @@ where (size, Some(size)) } } -/// An iterator over a range of indices but without +/// An iterator over a range of indices that owns the range, rather than borrowing it. /// /// Because I really played myself by making the [IndexRangeIterator] have a -/// lifetime attached to it. This one doesn't do that. As with it's sibling, the +/// lifetime attached to it. This one doesn't do that. As with its sibling, the /// range is half open, meaning that the start is inclusive, but the end is /// exclusive. pub struct OwnedIndexRangeIterator @@ -253,7 +293,6 @@ where I: IndexRef + PartialOrd, { range: IndexRange, - current: I, } impl OwnedIndexRangeIterator @@ -261,10 +300,7 @@ where I: IndexRef + PartialOrd, { pub fn new(range: IndexRange) -> Self { - Self { - range, - current: range.start, - } + Self { range } } } @@ -275,18 +311,18 @@ where type Item = I; fn next(&mut self) -> Option { - if self.current < self.range.end { - let current = self.current; - self.current = I::new(self.current.index() + 1); - Some(current) + if self.range.start < self.range.end { + let out = self.range.start; + self.range.start = I::new(self.range.start.index() + 1); + Some(out) } else { None } } fn size_hint(&self) -> (usize, Option) { - let size = if self.range.end.index() > self.current.index() { - self.range.end.index() - self.current.index() + let size = if self.range.end > self.range.start { + self.range.end.index() - self.range.start.index() } else { 0 }; diff --git a/interp/src/flatten/structures/indexed_map.rs b/interp/src/flatten/structures/indexed_map.rs index e3f24c3150..58b1e7a135 100644 --- a/interp/src/flatten/structures/indexed_map.rs +++ b/interp/src/flatten/structures/indexed_map.rs @@ -121,6 +121,10 @@ where self.data.iter_mut() } + pub fn values(&self) -> impl Iterator { + self.data.iter() + } + pub fn keys(&self) -> impl Iterator + '_ { // TODO (griffin): Make this an actual struct instead self.data.iter().enumerate().map(|(i, _)| K::new(i)) @@ -153,15 +157,12 @@ where data: &'data IndexedMap, } -impl<'range, 'data, K, D> ExactSizeIterator - for IndexedMapRangeIterator<'range, 'data, K, D> -where - K: IndexRef + PartialOrd, +impl ExactSizeIterator for IndexedMapRangeIterator<'_, '_, K, D> where + K: IndexRef + PartialOrd { } -impl<'range, 'data, K, D> Iterator - for IndexedMapRangeIterator<'range, 'data, K, D> +impl<'data, K, D> Iterator for IndexedMapRangeIterator<'_, 'data, K, D> where K: IndexRef + PartialOrd, { diff --git a/interp/src/flatten/structures/thread.rs b/interp/src/flatten/structures/thread.rs index 8ca8a5bc9e..85a70fb5fe 100644 --- a/interp/src/flatten/structures/thread.rs +++ b/interp/src/flatten/structures/thread.rs @@ -31,18 +31,27 @@ pub struct ThreadMap { } impl ThreadMap { - pub fn new(root_clock: ClockIdx) -> Self { + pub fn new(root_clock: ClockIdx, continuous_clock: ClockIdx) -> Self { let mut map = IndexedMap::new(); map.push(ThreadInfo { parent: None, clock_id: root_clock, }); + map.push(ThreadInfo { + parent: None, + clock_id: continuous_clock, + }); Self { map } } pub fn root_thread() -> ThreadIdx { ThreadIdx::from(0) } + + pub fn continuous_thread() -> ThreadIdx { + ThreadIdx::from(1) + } + /// Lookup the clock associated with the given thread id. Returns `None` if /// the thread id is invalid. pub fn get_clock_id(&self, thread_id: &ThreadIdx) -> Option { diff --git a/interp/tests/data-race/conflict.expect b/interp/tests/data-race/conflict.expect new file mode 100644 index 0000000000..fdf4dca079 --- /dev/null +++ b/interp/tests/data-race/conflict.expect @@ -0,0 +1,4 @@ +---CODE--- +1 +---STDERR--- +Error: Concurrent read & write to the same register main.count diff --git a/interp/tests/data-race/conflict.futil b/interp/tests/data-race/conflict.futil new file mode 100644 index 0000000000..335ac9bc35 --- /dev/null +++ b/interp/tests/data-race/conflict.futil @@ -0,0 +1,66 @@ +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; +import "primitives/binary_operators.futil"; + +component main() -> (out: 32) { + cells { + pow = std_reg(32); + count = std_reg(4); + mul = std_mult_pipe(32); + lt = std_lt(4); + incr = std_add(4); + const0 = std_const(4, 3); + base = std_reg(32); + exp = std_reg(4); + my_wire = std_wire(1); + useless_wire = std_wire(1); + } + wires { + group init<"static"=1> { + pow.in = 32'd1; + pow.write_en = 1'd1; + count.in = 4'd0; + count.write_en = 1'd1; + + base.in = 32'd10; + base.write_en = 1'd1; + + exp.in = 4'd3; + exp.write_en = 1'd1; + + + init[done] = pow.done & count.done ? 1'd1; + } + group do_mul { + mul.left = base.out; + mul.right = pow.out; + mul.go = !mul.done ? 1'd1; + pow.in = mul.out; + pow.write_en = mul.done; + useless_wire.in = my_wire.out; + do_mul[done] = pow.done; + } + group incr_count<"static"=1> { + incr.left = 4'd1; + incr.right = count.out; + count.in = incr.out; + count.write_en = 1'd1; + incr_count[done] = count.done; + } + comb group cond { + lt.right = exp.out; + lt.left = count.out; + my_wire.in = !lt.out & 1'd0 ? 1'd1; + } + + out = pow.out; + } + control { + seq { + init; + while lt.out with cond { + par { do_mul; incr_count; } + } + } + } +} diff --git a/interp/tests/data-race/continuous-race.expect b/interp/tests/data-race/continuous-race.expect new file mode 100644 index 0000000000..d6eeceb5f8 --- /dev/null +++ b/interp/tests/data-race/continuous-race.expect @@ -0,0 +1,4 @@ +---CODE--- +1 +---STDERR--- +Error: Concurrent read & write to the same register main.val diff --git a/interp/tests/data-race/continuous-race.futil b/interp/tests/data-race/continuous-race.futil new file mode 100644 index 0000000000..03fcc56bf4 --- /dev/null +++ b/interp/tests/data-race/continuous-race.futil @@ -0,0 +1,28 @@ +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; + +component main() -> () { + cells { + @external mem = comb_mem_d1(32, 20, 32); + val = std_reg(32); + add = std_add(32); + } + wires { + add.right = 32'b1; + add.left = val.out; + val.write_en = 1'b1; + val.in = add.out; + + group store { + mem.addr0 = val.out; + mem.write_data = val.out; + mem.write_en = 1'b1; + store[done] = mem.done; + } + } + control { + repeat 19 { + store; + } + } +} diff --git a/interp/tests/data-race/guard-conflict.expect b/interp/tests/data-race/guard-conflict.expect index d3f49ee6dd..cc56d3b526 100644 --- a/interp/tests/data-race/guard-conflict.expect +++ b/interp/tests/data-race/guard-conflict.expect @@ -1,4 +1,4 @@ ---CODE--- 1 ---STDERR--- -Error: Concurrent read & write to the same register/memory main.cond_reg +Error: Concurrent read & write to the same register main.cond_reg diff --git a/interp/tests/data-race/par-conflict-cmem.expect b/interp/tests/data-race/par-conflict-cmem.expect index 59aad0e0bd..ed1403c818 100644 --- a/interp/tests/data-race/par-conflict-cmem.expect +++ b/interp/tests/data-race/par-conflict-cmem.expect @@ -1,4 +1,4 @@ ---CODE--- 1 ---STDERR--- -Error: Concurrent read & write to the same register/memory main.cond_mem +Error: Concurrent read & write to the same memory main.cond_mem in slot 0 diff --git a/interp/tests/data-race/par-conflict.expect b/interp/tests/data-race/par-conflict.expect index d3f49ee6dd..cc56d3b526 100644 --- a/interp/tests/data-race/par-conflict.expect +++ b/interp/tests/data-race/par-conflict.expect @@ -1,4 +1,4 @@ ---CODE--- 1 ---STDERR--- -Error: Concurrent read & write to the same register/memory main.cond_reg +Error: Concurrent read & write to the same register main.cond_reg diff --git a/interp/tests/data-race/strange_mult.expect b/interp/tests/data-race/strange_mult.expect new file mode 100644 index 0000000000..f96745bfa8 --- /dev/null +++ b/interp/tests/data-race/strange_mult.expect @@ -0,0 +1,4 @@ +---CODE--- +1 +---STDERR--- +Error: Concurrent read & write to the same register main.first_reg diff --git a/interp/tests/data-race/strange_mult.futil b/interp/tests/data-race/strange_mult.futil new file mode 100644 index 0000000000..8d3c43f4dd --- /dev/null +++ b/interp/tests/data-race/strange_mult.futil @@ -0,0 +1,50 @@ +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; +import "primitives/binary_operators.futil"; + +component main() -> (out: 32) { + cells { + first_reg = std_reg(32); + mult = std_mult_pipe(32); + second_reg = std_reg(32); + adder = std_add(32); + adder2 = std_add(32); + } + wires { + group init { + first_reg.in = 32'd10; + first_reg.write_en = 1'd1; + second_reg.in = 32'd20; + second_reg.write_en = 1'd1; + init[done] = first_reg.done; + } + + group incr_first_reg { + adder.left = first_reg.out; + adder.right = 32'd1; + first_reg.in = adder.out; + first_reg.write_en = 1'd1; + incr_first_reg[done] = first_reg.done; + } + + group incr_second_reg { + adder2.left = second_reg.out; + adder2.right = 32'd1; + second_reg.in = adder2.out; + second_reg.write_en = 1'd1; + incr_second_reg[done] = second_reg.done; + } + + mult.left = first_reg.out; + mult.right = second_reg.out; + } + control { + seq { + init; + par { + incr_first_reg; + incr_second_reg; + } + } + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000..2e2b8c8521 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.82.0" diff --git a/tools/cider-data-converter/Cargo.toml b/tools/cider-data-converter/Cargo.toml index a5755b4066..a690023d34 100644 --- a/tools/cider-data-converter/Cargo.toml +++ b/tools/cider-data-converter/Cargo.toml @@ -17,6 +17,7 @@ thiserror = "1.0.59" num-bigint = { version = "0.4.6" } num-rational = { version = "0.4.2" } num-traits = { version = "0.2.19" } +nom = "7.1.3" [dev-dependencies] proptest = "1.0.0" diff --git a/tools/cider-data-converter/src/dat_parser.rs b/tools/cider-data-converter/src/dat_parser.rs new file mode 100644 index 0000000000..88f99d6ba6 --- /dev/null +++ b/tools/cider-data-converter/src/dat_parser.rs @@ -0,0 +1,133 @@ +use nom::{ + branch::alt, + bytes::complete::{tag, take_while_m_n}, + character::complete::{anychar, line_ending, multispace0}, + combinator::{eof, map_res, opt}, + error::Error, + multi::{many1, many_till}, + sequence::{preceded, tuple}, + IResult, +}; + +fn is_hex_digit(c: char) -> bool { + c.is_ascii_hexdigit() +} + +fn from_hex(input: &str) -> Result { + u8::from_str_radix(input, 16) +} + +fn parse_hex(input: &str) -> IResult<&str, u8> { + map_res(take_while_m_n(1, 2, is_hex_digit), from_hex)(input) +} + +/// Parse a single line of hex characters into a vector of bytes in the order +/// the characters are given, i.e. reversed. +fn hex_line(input: &str) -> IResult<&str, LineOrComment> { + // strip any leading whitespace + let (input, bytes) = preceded( + tuple((multispace0, opt(tag("0x")))), + many1(parse_hex), + )(input)?; + + Ok((input, LineOrComment::Line(bytes))) +} + +fn comment(input: &str) -> IResult<&str, LineOrComment> { + // skip any whitespace + let (input, _) = multispace0(input)?; + let (input, _) = tag("//")(input)?; + let (input, _) = many_till(anychar, alt((line_ending, eof)))(input)?; + Ok((input, LineOrComment::Comment)) +} +/// Parse a line which only contains whitespace +fn empty_line(input: &str) -> IResult<&str, LineOrComment> { + // skip any whitespace + let (input, _) = multispace0(input)?; + Ok((input, LineOrComment::EmptyLine)) +} + +pub fn line_or_comment( + input: &str, +) -> Result>> { + let (_, res) = alt((hex_line, comment, empty_line))(input)?; + Ok(res) +} + +#[derive(Debug, PartialEq)] +pub enum LineOrComment { + Line(Vec), + Comment, + EmptyLine, +} + +/// Parse a single line of hex characters, or a comment. Returns None if it's a +/// comment or an empty line and Some(Vec) if it's a hex line. Panics on a +/// parse error. +/// +/// For the fallible version, see `line_or_comment`. +pub fn unwrap_line_or_comment(input: &str) -> Option> { + match line_or_comment(input).expect("hex parse failed") { + LineOrComment::Line(vec) => Some(vec), + LineOrComment::Comment => None, + LineOrComment::EmptyLine => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_comment() { + assert_eq!(comment("// comment"), Ok(("", LineOrComment::Comment))); + assert_eq!(comment("// comment\n"), Ok(("", LineOrComment::Comment))); + } + + #[test] + fn test_hex_line() { + assert_eq!(hex_line("0x01"), Ok(("", LineOrComment::Line(vec![1])))); + assert_eq!(hex_line("0x02"), Ok(("", LineOrComment::Line(vec![2])))); + assert_eq!(hex_line("0x03"), Ok(("", LineOrComment::Line(vec![3])))); + assert_eq!(hex_line("0x04"), Ok(("", LineOrComment::Line(vec![4])))); + assert_eq!(hex_line("0x05"), Ok(("", LineOrComment::Line(vec![5])))); + assert_eq!(hex_line("0x06"), Ok(("", LineOrComment::Line(vec![6])))); + assert_eq!(hex_line("0x07"), Ok(("", LineOrComment::Line(vec![7])))); + assert_eq!(hex_line("0x08"), Ok(("", LineOrComment::Line(vec![8])))); + assert_eq!(hex_line("0x09"), Ok(("", LineOrComment::Line(vec![9])))); + assert_eq!(hex_line("0x0a"), Ok(("", LineOrComment::Line(vec![10])))); + assert_eq!(hex_line("0x0b"), Ok(("", LineOrComment::Line(vec![11])))); + assert_eq!(hex_line("0x0c"), Ok(("", LineOrComment::Line(vec![12])))); + assert_eq!(hex_line("0x0d"), Ok(("", LineOrComment::Line(vec![13])))); + assert_eq!(hex_line("0x0e"), Ok(("", LineOrComment::Line(vec![14])))); + assert_eq!(hex_line("0x0f"), Ok(("", LineOrComment::Line(vec![15])))); + assert_eq!(hex_line("0xff"), Ok(("", LineOrComment::Line(vec![255])))); + assert_eq!( + hex_line("0x00ff"), + Ok(("", LineOrComment::Line(vec![0, 255]))) + ); + } + + #[test] + fn test_from_hex() { + assert_eq!(from_hex("0"), Ok(0)); + assert_eq!(from_hex("1"), Ok(1)); + assert_eq!(from_hex("2"), Ok(2)); + assert_eq!(from_hex("3"), Ok(3)); + assert_eq!(from_hex("4"), Ok(4)); + assert_eq!(from_hex("5"), Ok(5)); + assert_eq!(from_hex("6"), Ok(6)); + assert_eq!(from_hex("7"), Ok(7)); + assert_eq!(from_hex("8"), Ok(8)); + assert_eq!(from_hex("9"), Ok(9)); + assert_eq!(from_hex("a"), Ok(10)); + assert_eq!(from_hex("b"), Ok(11)); + assert_eq!(from_hex("c"), Ok(12)); + assert_eq!(from_hex("d"), Ok(13)); + assert_eq!(from_hex("e"), Ok(14)); + assert_eq!(from_hex("f"), Ok(15)); + + assert_eq!(from_hex("FF"), Ok(255)); + assert_eq!(from_hex("ff"), Ok(255)); + } +} diff --git a/tools/cider-data-converter/src/lib.rs b/tools/cider-data-converter/src/lib.rs index 7e1dadc1bd..31092d73e6 100644 --- a/tools/cider-data-converter/src/lib.rs +++ b/tools/cider-data-converter/src/lib.rs @@ -1,2 +1,3 @@ pub mod converter; +pub mod dat_parser; pub mod json_data; diff --git a/tools/cider-data-converter/src/main.rs b/tools/cider-data-converter/src/main.rs index e44fea9cb6..a95d910e83 100644 --- a/tools/cider-data-converter/src/main.rs +++ b/tools/cider-data-converter/src/main.rs @@ -1,11 +1,13 @@ use argh::FromArgs; -use cider_data_converter::{converter, json_data::JsonData}; +use cider_data_converter::{ + converter, dat_parser::unwrap_line_or_comment, json_data::JsonData, +}; use core::str; use interp::serialization::{self, DataDump, SerializationError}; -use itertools::Itertools; use std::{ fs::File, io::{self, BufRead, BufReader, BufWriter, Read, Write}, + iter::repeat, path::PathBuf, str::FromStr, }; @@ -13,6 +15,7 @@ use thiserror::Error; const JSON_EXTENSION: &str = "data"; const CIDER_EXTENSION: &str = "dump"; +const DAT_EXTENSION: &str = "dat"; const HEADER_FILENAME: &str = "header"; @@ -32,6 +35,14 @@ enum CiderDataConverterError { #[error(transparent)] DataDumpError(#[from] SerializationError), + + #[error( + "Missing output path. This is required for the \"to dat\" conversion" + )] + MissingDatOutputPath, + + #[error("Output path for \"to dat\" exists but it is a file")] + DatOutputPathIsFile, } impl std::fmt::Debug for CiderDataConverterError { @@ -90,6 +101,12 @@ struct Opts { /// exists solely for backwards compatibility with the old display format. #[argh(switch, long = "legacy-quotes")] use_quotes: bool, + + /// the file extension to use for the output/input file when parsing to and + /// from the dat target. If not provided, the extension is assumed to be .dat + #[argh(option, short = 'e', long = "dat-file-extension")] + #[argh(default = "String::from(DAT_EXTENSION)")] + file_extension: String, } fn main() -> Result<(), CiderDataConverterError> { @@ -97,19 +114,27 @@ fn main() -> Result<(), CiderDataConverterError> { // if no action is specified, try to guess based on file extensions if opts.action.is_none() + // input is .json && (opts.input_path.as_ref().is_some_and(|x| { x.extension().map_or(false, |y| y == JSON_EXTENSION) - }) || opts.output_path.as_ref().is_some_and(|x| { + }) + // output is .dump + || opts.output_path.as_ref().is_some_and(|x| { x.extension().map_or(false, |y| y == CIDER_EXTENSION) })) { opts.action = Some(Target::DataDump); } else if opts.action.is_none() + // output is .json && (opts.output_path.as_ref().is_some_and(|x| { x.extension().map_or(false, |x| x == JSON_EXTENSION) - }) || opts.input_path.as_ref().is_some_and(|x| { + }) + // input is .dump + || opts.input_path.as_ref().is_some_and(|x| { x.extension().map_or(false, |x| x == CIDER_EXTENSION) - })) + }) + // input is a directory (suggesting a deserialization from dat) + || opts.input_path.as_ref().is_some_and(|x| x.is_dir())) { opts.action = Some(Target::Json); } @@ -144,30 +169,31 @@ fn main() -> Result<(), CiderDataConverterError> { for mem_dec in &header.memories { let starting_len = data.len(); let mem_file = BufReader::new(File::open( - path.join(&mem_dec.name), + path.join(format!( + "{}.{}", + mem_dec.name, opts.file_extension + )), )?); - let mut line_data = vec![]; for line in mem_file.lines() { let line = line?; - for pair in &line.chars().chunks(2) { - // there has got to be a better way to do this... - let string = - pair.into_iter().collect::(); - let val = u8::from_str_radix(&string, 16) - .expect("invalid hex"); - line_data.push(val); + if let Some(line_data) = + unwrap_line_or_comment(&line) + { + assert!( + line_data.len() + <= mem_dec.bytes_per_entry() + as usize, + "line data too long" + ); + + let padding = (mem_dec.bytes_per_entry() + as usize) + - line_data.len(); + + data.extend(line_data.into_iter().rev()); + data.extend(repeat(0u8).take(padding)) } - // TODO griffin: handle inputs that are - // truncated or otherwise shorter than expected - - assert!( - line_data.len() - == (mem_dec.bytes_per_entry() as usize) - ); - // reverse the byte order to get the expected - // little endian and reuse the vec - data.extend(line_data.drain(..).rev()) } assert_eq!( @@ -213,17 +239,22 @@ fn main() -> Result<(), CiderDataConverterError> { if let Some(path) = opts.output_path { if path.exists() && !path.is_dir() { - // TODO griffin: Make this an actual error - panic!("Output path exists but is not a directory") + return Err( + CiderDataConverterError::DatOutputPathIsFile, + ); } else if !path.exists() { std::fs::create_dir(&path)?; } - let mut header_output = File::create(path.join("header"))?; + let mut header_output = + File::create(path.join(HEADER_FILENAME))?; header_output.write_all(&data.header.serialize()?)?; for memory in &data.header.memories { - let file = File::create(path.join(&memory.name))?; + let file = File::create(path.join(format!( + "{}.{}", + memory.name, opts.file_extension + )))?; let mut writer = BufWriter::new(file); for bytes in data .get_data(&memory.name) @@ -243,8 +274,7 @@ fn main() -> Result<(), CiderDataConverterError> { } } } else { - // TODO griffin: Make this an actual error - panic!("Output path not specified, this is required for the dat target") + return Err(CiderDataConverterError::MissingDatOutputPath); } } } diff --git a/tools/data-conversion/src/float_special_values b/tools/data-conversion/src/float_special_values new file mode 100644 index 0000000000..96aadf84ef --- /dev/null +++ b/tools/data-conversion/src/float_special_values @@ -0,0 +1,32 @@ +use num_bigint::BigUint; +use std::str::FromStr; + +pub struct IntermediateRepresentation { + pub sign: bool, + pub mantissa: BigUint, + pub exponent: i64, // Arbitrary precision exponent +} + +impl IntermediateRepresentation { + // Function to check if the value is NaN + pub fn is_nan(&self, bit_width: usize) -> bool { + let max_exponent_value = (1 << (bit_width - 1)) - 1; // Max exponent for NaN + self.exponent == max_exponent_value as i64 && !self.mantissa.is_zero() + } + + // Function to check if the value is infinity + pub fn is_infinity(&self, bit_width: usize) -> bool { + let max_exponent_value = (1 << (bit_width - 1)) - 1; // Max exponent for infinity + self.exponent == max_exponent_value as i64 && self.mantissa.is_zero() + } + + // Function to check if the value is denormalized + pub fn is_denormalized(&self) -> bool { + self.exponent == 0 && !self.mantissa.is_zero() + } + + // Function to check if the value is zero + pub fn is_zero(&self) -> bool { + self.exponent == 0 && self.mantissa.is_zero() + } +}