From 9a370489b6e151c7cf60113e513be8b11020b10e Mon Sep 17 00:00:00 2001 From: Ethan Uppal <113849268+ethanuppal@users.noreply.github.com> Date: Wed, 20 Nov 2024 16:44:05 -0500 Subject: [PATCH] Revert "Simplify PR" This reverts commit 3d21790c5630661dbc68078a56c45b7afc39e84c. --- tools/calyx-ffi-macro/src/lib.rs | 177 ++++++++++ tools/calyx-ffi/src/prelude.rs | 2 +- tools/calyx-ffi/test.rs | 479 ++++++++++++++++++++++++++++ tools/calyx-ffi/tests/adder.futil | 23 ++ tools/calyx-ffi/tests/arith_fuzz.rs | 74 +++++ tools/calyx-ffi/tests/fifo.futil | 130 ++++++++ tools/calyx-ffi/tests/fifo.rs | 69 ++++ tools/calyx-ffi/tests/stack.futil | 70 ++++ tools/calyx-ffi/tests/stack.rs | 61 ++++ tools/calyx-ffi/tests/subber.futil | 21 ++ 10 files changed, 1105 insertions(+), 1 deletion(-) create mode 100644 tools/calyx-ffi/test.rs create mode 100644 tools/calyx-ffi/tests/adder.futil create mode 100644 tools/calyx-ffi/tests/arith_fuzz.rs create mode 100644 tools/calyx-ffi/tests/fifo.futil create mode 100644 tools/calyx-ffi/tests/fifo.rs create mode 100644 tools/calyx-ffi/tests/stack.futil create mode 100644 tools/calyx-ffi/tests/stack.rs create mode 100644 tools/calyx-ffi/tests/subber.futil diff --git a/tools/calyx-ffi-macro/src/lib.rs b/tools/calyx-ffi-macro/src/lib.rs index 1ab0bed1b..8cc6bffa5 100644 --- a/tools/calyx-ffi-macro/src/lib.rs +++ b/tools/calyx-ffi-macro/src/lib.rs @@ -239,3 +239,180 @@ pub fn calyx_ffi(attrs: TokenStream, item: TokenStream) -> TokenStream { } .into() } + +/// Extracts tests marked with `#[calyx_ffi_test]` and generates wrapper `#[test]`s and modules. +#[derive(Default)] +struct CalyxFFITestModuleVisitor { + pub wrappers: Vec, + pub tests: Vec, +} + +impl syn::visit::Visit<'_> for CalyxFFITestModuleVisitor { + fn visit_item_fn(&mut self, i: &syn::ItemFn) { + let has_calyx_ffi_test = i + .attrs + .iter() + .any(|attr| attr.path().is_ident("calyx_ffi_test")); + if has_calyx_ffi_test { + let fn_name = &i.sig.ident; + let dut_type = get_ffi_test_dut_type(i).expect( + "calyx_ffi_test should enforce that the type is well-formed", + ); + + self.wrappers.push(syn::parse_quote! { + pub(crate) unsafe fn #fn_name(ffi: &mut CalyxFFI) { + let dut = ffi.new_comp::<#dut_type>(); + let dut_ref = &mut *dut.borrow_mut(); + let dut_pointer = dut_ref as *mut dyn CalyxFFIComponent as *mut _ as *mut #dut_type; + let dut_concrete: &mut #dut_type = &mut *dut_pointer; + super::#fn_name(dut_concrete); + } + }); + self.tests.push(syn::parse_quote! { + #[test] + pub(crate) fn #fn_name() { + let mut ffi = CalyxFFI::new(); + unsafe { + super::calyx_ffi_generated_wrappers::#fn_name(&mut ffi); + } + } + }); + } + } +} + +#[proc_macro_attribute] +pub fn calyx_ffi_tests(args: TokenStream, item: TokenStream) -> TokenStream { + if !args.is_empty() { + return util::compile_error( + &args.into_iter().next().unwrap().span().into(), + "#[calyx_ffi_tests] takes no arguments".into(), + ); + } + + let mut module = parse_macro_input!(item as syn::ItemMod); + let module_name = &module.ident; + + let mut visitor = CalyxFFITestModuleVisitor::default(); + syn::visit::visit_item_mod(&mut visitor, &module); + let wrappers = visitor.wrappers; + let tests = visitor.tests; + + let test_names = wrappers.iter().map(|test| test.sig.ident.clone()); + let generated_wrappers = quote! { + pub(crate) mod calyx_ffi_generated_wrappers { + use super::*; + + pub(crate) const CALYX_FFI_TESTS: &'static [unsafe fn(&mut CalyxFFI) -> ()] = &[ + #(#test_names),* + ]; + + #(#wrappers)* + } + }; + let generated_wrappers_item: syn::Item = + syn::parse2(generated_wrappers).unwrap(); + + let generated_tests = quote! { + pub(crate) mod calyx_ffi_generated_tests { + use super::*; + + #(#tests)* + } + }; + let generated_tests_item: syn::Item = syn::parse2(generated_tests).unwrap(); + + let items_to_add = vec![generated_wrappers_item, generated_tests_item]; + if let Some((_, ref mut items)) = module.content { + items.extend(items_to_add); + } else { + module.content = Some((syn::token::Brace::default(), items_to_add)); + } + + quote! { + #module + + pub mod calyx_ffi_generated_top { + use super::*; + + pub unsafe fn run_tests(ffi: &mut CalyxFFI) { + for test in #module_name::calyx_ffi_generated_wrappers::CALYX_FFI_TESTS { + test(ffi); + } + } + } + } + .into() +} + +#[proc_macro_attribute] +pub fn calyx_ffi_test(args: TokenStream, item: TokenStream) -> TokenStream { + if !args.is_empty() { + return util::compile_error( + &args.into_iter().next().unwrap().span().into(), + "#[calyx_ffi_test] takes no arguments".into(), + ); + } + + let mut func = parse_macro_input!(item as syn::ItemFn); + let dut_type = get_ffi_test_dut_type(&func); + let Ok(dut_type) = dut_type else { + return dut_type.err().unwrap(); + }; + + let check_trait_impl = quote! { + { + fn assert_is_calyx_ffi_component() {} + assert_is_calyx_ffi_component::<#dut_type>(); + } + }; + + let check_trait_impl_stmts: syn::Block = syn::parse2(check_trait_impl) + .expect("Failed to parse check_trait_impl as a block"); + + let new_stmts: Vec = check_trait_impl_stmts + .stmts + .iter() + .chain(func.block.stmts.iter()) + .cloned() + .collect(); + + let new_block = syn::Block { + brace_token: func.block.brace_token, + stmts: new_stmts, + }; + func.block = Box::new(new_block); + + quote! { + #func + } + .into() +} + +fn get_ffi_test_dut_type( + func: &syn::ItemFn, +) -> Result<&syn::Type, TokenStream> { + let inputs: Vec<&syn::FnArg> = func.sig.inputs.iter().collect(); + + let bad_sig_msg = "#[calyx_ffi_test] tests must take exactly one argument, namely, a mutable reference to the DUT".into(); + + if inputs.len() != 1 { + return Err(util::compile_error(&func.span(), bad_sig_msg)); + } + let input = inputs.first().unwrap(); + + let syn::FnArg::Typed(pat_ty) = input else { + return Err(util::compile_error(&func.span(), bad_sig_msg)); + }; + + let syn::Type::Reference(syn::TypeReference { + mutability: Some(syn::token::Mut { span: _ }), + ref elem, + .. + }) = *pat_ty.ty + else { + return Err(util::compile_error(&func.span(), bad_sig_msg)); + }; + + Ok(elem) +} diff --git a/tools/calyx-ffi/src/prelude.rs b/tools/calyx-ffi/src/prelude.rs index 592e19026..accb64d95 100644 --- a/tools/calyx-ffi/src/prelude.rs +++ b/tools/calyx-ffi/src/prelude.rs @@ -2,7 +2,7 @@ pub use super::{ interface::{CalyxFFI, CalyxFFIComponent, CalyxFFIComponentRef}, value::Value, }; -pub use calyx_ffi_macro::calyx_ffi; +pub use calyx_ffi_macro::{calyx_ffi, calyx_ffi_test, calyx_ffi_tests}; pub use calyx_ir; pub use interp; pub use paste; diff --git a/tools/calyx-ffi/test.rs b/tools/calyx-ffi/test.rs new file mode 100644 index 000000000..718f4c055 --- /dev/null +++ b/tools/calyx-ffi/test.rs @@ -0,0 +1,479 @@ +#![feature(prelude_import)] +#[prelude_import] +use std::prelude::rust_2021::*; +#[macro_use] +extern crate std; +use calyx_ffi::prelude::*; +use calyx_ffi::cider_ffi_backend; +pub trait In2Out1: CalyxFFIComponent { + fn lhs_bits(&mut self) -> &mut calyx_ffi::value::Value<64>; + fn set_lhs(&mut self, value: u64); + fn rhs_bits(&mut self) -> &mut calyx_ffi::value::Value<64>; + fn set_rhs(&mut self, value: u64); + fn result_bits(&self) -> &calyx_ffi::value::Value<64>; + fn result(&self) -> u64; +} +struct Adder { + pub lhs: calyx_ffi::value::Value<64u64>, + pub rhs: calyx_ffi::value::Value<64u64>, + result: calyx_ffi::value::Value<64u64>, + pub go: calyx_ffi::value::Value<1u64>, + pub clk: calyx_ffi::value::Value<1u64>, + pub reset: calyx_ffi::value::Value<1u64>, + done: calyx_ffi::value::Value<1u64>, + user_data: std::mem::MaybeUninit<::calyx_ffi::backend::cider::CiderFFIBackend>, +} +impl Adder { + pub const fn lhs_width() -> calyx_ffi::value::WidthInt { + 64u64 as calyx_ffi::value::WidthInt + } + pub const fn rhs_width() -> calyx_ffi::value::WidthInt { + 64u64 as calyx_ffi::value::WidthInt + } + pub const fn result_width() -> calyx_ffi::value::WidthInt { + 64u64 as calyx_ffi::value::WidthInt + } + pub const fn go_width() -> calyx_ffi::value::WidthInt { + 1u64 as calyx_ffi::value::WidthInt + } + pub const fn clk_width() -> calyx_ffi::value::WidthInt { + 1u64 as calyx_ffi::value::WidthInt + } + pub const fn reset_width() -> calyx_ffi::value::WidthInt { + 1u64 as calyx_ffi::value::WidthInt + } + pub const fn done_width() -> calyx_ffi::value::WidthInt { + 1u64 as calyx_ffi::value::WidthInt + } + pub fn result(&self) -> u64 { + (&self.result).try_into().expect("port value wider than 64 bits") + } + pub const fn result_bits(&self) -> &calyx_ffi::value::Value<64u64> { + &self.result + } + pub fn done(&self) -> u64 { + (&self.done).try_into().expect("port value wider than 64 bits") + } + pub const fn done_bits(&self) -> &calyx_ffi::value::Value<1u64> { + &self.done + } + pub fn set_lhs(&mut self, value: u64) { + self.lhs = calyx_ffi::value::Value::from(value); + } + pub fn set_rhs(&mut self, value: u64) { + self.rhs = calyx_ffi::value::Value::from(value); + } + pub fn set_go(&mut self, value: u64) { + self.go = calyx_ffi::value::Value::from(value); + } + pub fn set_clk(&mut self, value: u64) { + self.clk = calyx_ffi::value::Value::from(value); + } + pub fn set_reset(&mut self, value: u64) { + self.reset = calyx_ffi::value::Value::from(value); + } +} +impl std::default::Default for Adder { + fn default() -> Self { + Self { + lhs: calyx_ffi::value::Value::from(0), + rhs: calyx_ffi::value::Value::from(0), + result: calyx_ffi::value::Value::from(0), + go: calyx_ffi::value::Value::from(0), + clk: calyx_ffi::value::Value::from(0), + reset: calyx_ffi::value::Value::from(0), + done: calyx_ffi::value::Value::from(0), + user_data: unsafe { std::mem::MaybeUninit::zeroed() }, + } + } +} +impl std::clone::Clone for Adder { + fn clone(&self) -> Self { + Self { + lhs: self.lhs.clone(), + rhs: self.rhs.clone(), + result: self.result.clone(), + go: self.go.clone(), + clk: self.clk.clone(), + reset: self.reset.clone(), + done: self.done.clone(), + user_data: unsafe { + std::mem::MaybeUninit::new(self.user_data.assume_init_ref().clone()) + }, + } + } +} +impl CalyxFFIComponent for Adder { + fn path(&self) -> &'static str { + "/Users/ethan/Documents/GitHub/calyx/tools/calyx-ffi/tests/adder.futil" + } + fn name(&self) -> &'static str { + "main" + } + fn init(&mut self, context: &calyx_ir::Context) { + self.user_data + .write( + ::calyx_ffi::backend::cider::CiderFFIBackend::from(context, self.name()), + ); + } + fn reset(&mut self) { + { + ::std::io::_print( + format_args!("cider_ffi_backend reset. doesn\'t work LOL\n"), + ); + }; + } + fn can_tick(&self) -> bool { + true + } + fn tick(&mut self) { + let cider = unsafe { self.user_data.assume_init_mut() }; + cider.write_port("lhs", &self.lhs.inner); + cider.write_port("rhs", &self.rhs.inner); + cider.write_port("go", &self.go.inner); + cider.write_port("clk", &self.clk.inner); + cider.write_port("reset", &self.reset.inner); + cider.step(); + self.result.inner = cider.read_port("result"); + self.done.inner = cider.read_port("done"); + } + fn go(&mut self) { + let cider = unsafe { self.user_data.assume_init_mut() }; + cider.write_port("lhs", &self.lhs.inner); + cider.write_port("rhs", &self.rhs.inner); + cider.write_port("go", &self.go.inner); + cider.write_port("clk", &self.clk.inner); + cider.write_port("reset", &self.reset.inner); + cider.go(); + self.result.inner = cider.read_port("result"); + self.done.inner = cider.read_port("done"); + } +} +impl In2Out1 for Adder { + fn result_bits(&self) -> &calyx_ffi::value::Value<64> { + &self.result + } + fn result(&self) -> u64 { + Self::result(self) + } + fn lhs_bits(&mut self) -> &mut calyx_ffi::value::Value<64> { + &mut self.lhs + } + fn set_lhs(&mut self, value: u64) { + Self::set_lhs(self, value); + } + fn rhs_bits(&mut self) -> &mut calyx_ffi::value::Value<64> { + &mut self.rhs + } + fn set_rhs(&mut self, value: u64) { + Self::set_rhs(self, value); + } +} +struct Subber { + pub lhs: calyx_ffi::value::Value<64u64>, + pub rhs: calyx_ffi::value::Value<64u64>, + result: calyx_ffi::value::Value<64u64>, + pub go: calyx_ffi::value::Value<1u64>, + pub clk: calyx_ffi::value::Value<1u64>, + pub reset: calyx_ffi::value::Value<1u64>, + done: calyx_ffi::value::Value<1u64>, + user_data: std::mem::MaybeUninit<::calyx_ffi::backend::cider::CiderFFIBackend>, +} +impl Subber { + pub const fn lhs_width() -> calyx_ffi::value::WidthInt { + 64u64 as calyx_ffi::value::WidthInt + } + pub const fn rhs_width() -> calyx_ffi::value::WidthInt { + 64u64 as calyx_ffi::value::WidthInt + } + pub const fn result_width() -> calyx_ffi::value::WidthInt { + 64u64 as calyx_ffi::value::WidthInt + } + pub const fn go_width() -> calyx_ffi::value::WidthInt { + 1u64 as calyx_ffi::value::WidthInt + } + pub const fn clk_width() -> calyx_ffi::value::WidthInt { + 1u64 as calyx_ffi::value::WidthInt + } + pub const fn reset_width() -> calyx_ffi::value::WidthInt { + 1u64 as calyx_ffi::value::WidthInt + } + pub const fn done_width() -> calyx_ffi::value::WidthInt { + 1u64 as calyx_ffi::value::WidthInt + } + pub fn result(&self) -> u64 { + (&self.result).try_into().expect("port value wider than 64 bits") + } + pub const fn result_bits(&self) -> &calyx_ffi::value::Value<64u64> { + &self.result + } + pub fn done(&self) -> u64 { + (&self.done).try_into().expect("port value wider than 64 bits") + } + pub const fn done_bits(&self) -> &calyx_ffi::value::Value<1u64> { + &self.done + } + pub fn set_lhs(&mut self, value: u64) { + self.lhs = calyx_ffi::value::Value::from(value); + } + pub fn set_rhs(&mut self, value: u64) { + self.rhs = calyx_ffi::value::Value::from(value); + } + pub fn set_go(&mut self, value: u64) { + self.go = calyx_ffi::value::Value::from(value); + } + pub fn set_clk(&mut self, value: u64) { + self.clk = calyx_ffi::value::Value::from(value); + } + pub fn set_reset(&mut self, value: u64) { + self.reset = calyx_ffi::value::Value::from(value); + } +} +impl std::default::Default for Subber { + fn default() -> Self { + Self { + lhs: calyx_ffi::value::Value::from(0), + rhs: calyx_ffi::value::Value::from(0), + result: calyx_ffi::value::Value::from(0), + go: calyx_ffi::value::Value::from(0), + clk: calyx_ffi::value::Value::from(0), + reset: calyx_ffi::value::Value::from(0), + done: calyx_ffi::value::Value::from(0), + user_data: unsafe { std::mem::MaybeUninit::zeroed() }, + } + } +} +impl std::clone::Clone for Subber { + fn clone(&self) -> Self { + Self { + lhs: self.lhs.clone(), + rhs: self.rhs.clone(), + result: self.result.clone(), + go: self.go.clone(), + clk: self.clk.clone(), + reset: self.reset.clone(), + done: self.done.clone(), + user_data: unsafe { + std::mem::MaybeUninit::new(self.user_data.assume_init_ref().clone()) + }, + } + } +} +impl CalyxFFIComponent for Subber { + fn path(&self) -> &'static str { + "/Users/ethan/Documents/GitHub/calyx/tools/calyx-ffi/tests/subber.futil" + } + fn name(&self) -> &'static str { + "main" + } + fn init(&mut self, context: &calyx_ir::Context) { + self.user_data + .write( + ::calyx_ffi::backend::cider::CiderFFIBackend::from(context, self.name()), + ); + } + fn reset(&mut self) { + { + ::std::io::_print( + format_args!("cider_ffi_backend reset. doesn\'t work LOL\n"), + ); + }; + } + fn can_tick(&self) -> bool { + true + } + fn tick(&mut self) { + let cider = unsafe { self.user_data.assume_init_mut() }; + cider.write_port("lhs", &self.lhs.inner); + cider.write_port("rhs", &self.rhs.inner); + cider.write_port("go", &self.go.inner); + cider.write_port("clk", &self.clk.inner); + cider.write_port("reset", &self.reset.inner); + cider.step(); + self.result.inner = cider.read_port("result"); + self.done.inner = cider.read_port("done"); + } + fn go(&mut self) { + let cider = unsafe { self.user_data.assume_init_mut() }; + cider.write_port("lhs", &self.lhs.inner); + cider.write_port("rhs", &self.rhs.inner); + cider.write_port("go", &self.go.inner); + cider.write_port("clk", &self.clk.inner); + cider.write_port("reset", &self.reset.inner); + cider.go(); + self.result.inner = cider.read_port("result"); + self.done.inner = cider.read_port("done"); + } +} +impl In2Out1 for Subber { + fn result_bits(&self) -> &calyx_ffi::value::Value<64> { + &self.result + } + fn result(&self) -> u64 { + Self::result(self) + } + fn lhs_bits(&mut self) -> &mut calyx_ffi::value::Value<64> { + &mut self.lhs + } + fn set_lhs(&mut self, value: u64) { + Self::set_lhs(self, value); + } + fn rhs_bits(&mut self) -> &mut calyx_ffi::value::Value<64> { + &mut self.rhs + } + fn set_rhs(&mut self, value: u64) { + Self::set_rhs(self, value); + } +} +#[cfg(test)] +mod tests { + use std::mem; + use super::*; + use rand::Rng; + fn fuzz_in2out1 u64>(comp: &mut I, oracle: &F) { + comp.reset(); + let mut rng = rand::thread_rng(); + for (mut x, mut y) in (0..100).map(|_| (rng.gen(), rng.gen())) { + if y > x { + mem::swap(&mut x, &mut y); + } + comp.set_lhs(x); + comp.set_rhs(y); + comp.go(); + match (&oracle(x, y), &comp.result()) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + let kind = ::core::panicking::AssertKind::Eq; + ::core::panicking::assert_failed( + kind, + &*left_val, + &*right_val, + ::core::option::Option::Some( + format_args!( + "component did not evaluate f({0}, {1}) = {2} correctly", + x, + y, + oracle(x, y), + ), + ), + ); + } + } + }; + } + } + fn test_add(adder: &mut Adder) { + fn assert_is_calyx_ffi_component() {} + assert_is_calyx_ffi_component::(); + { + ::std::io::_print(format_args!("testing adder\n")); + }; + fuzz_in2out1(adder, &(|x, y| x.wrapping_add(y))) + } + fn test_sub(subber: &mut Subber) { + fn assert_is_calyx_ffi_component() {} + assert_is_calyx_ffi_component::(); + { + ::std::io::_print(format_args!("testing subber\n")); + }; + fuzz_in2out1(subber, &(|x, y| x - y)) + } + pub(crate) mod calyx_ffi_generated_wrappers { + use super::*; + pub(crate) const CALYX_FFI_TESTS: &'static [unsafe fn(&mut CalyxFFI) -> ()] = &[ + test_add, + test_sub, + ]; + pub(crate) unsafe fn test_add(ffi: &mut CalyxFFI) { + let dut = ffi.new_comp::(); + let dut_ref = &mut *dut.borrow_mut(); + let dut_pointer = dut_ref as *mut dyn CalyxFFIComponent as *mut _ + as *mut Adder; + let dut_concrete: &mut Adder = &mut *dut_pointer; + super::test_add(dut_concrete); + } + pub(crate) unsafe fn test_sub(ffi: &mut CalyxFFI) { + let dut = ffi.new_comp::(); + let dut_ref = &mut *dut.borrow_mut(); + let dut_pointer = dut_ref as *mut dyn CalyxFFIComponent as *mut _ + as *mut Subber; + let dut_concrete: &mut Subber = &mut *dut_pointer; + super::test_sub(dut_concrete); + } + } + pub(crate) mod calyx_ffi_generated_tests { + use super::*; + extern crate test; + #[cfg(test)] + #[rustc_test_marker = "tests::calyx_ffi_generated_tests::test_add"] + pub const test_add: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("tests::calyx_ffi_generated_tests::test_add"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "tools/calyx-ffi/tests/arith_fuzz.rs", + start_line: 64usize, + start_col: 8usize, + end_line: 64usize, + end_col: 16usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(test_add()), + ), + }; + pub(crate) fn test_add() { + let mut ffi = CalyxFFI::new(); + unsafe { + super::calyx_ffi_generated_wrappers::test_add(&mut ffi); + } + } + extern crate test; + #[cfg(test)] + #[rustc_test_marker = "tests::calyx_ffi_generated_tests::test_sub"] + pub const test_sub: test::TestDescAndFn = test::TestDescAndFn { + desc: test::TestDesc { + name: test::StaticTestName("tests::calyx_ffi_generated_tests::test_sub"), + ignore: false, + ignore_message: ::core::option::Option::None, + source_file: "tools/calyx-ffi/tests/arith_fuzz.rs", + start_line: 70usize, + start_col: 8usize, + end_line: 70usize, + end_col: 16usize, + compile_fail: false, + no_run: false, + should_panic: test::ShouldPanic::No, + test_type: test::TestType::IntegrationTest, + }, + testfn: test::StaticTestFn( + #[coverage(off)] + || test::assert_test_result(test_sub()), + ), + }; + pub(crate) fn test_sub() { + let mut ffi = CalyxFFI::new(); + unsafe { + super::calyx_ffi_generated_wrappers::test_sub(&mut ffi); + } + } + } +} +pub mod calyx_ffi_generated_top { + use super::*; + pub unsafe fn run_tests(ffi: &mut CalyxFFI) { + for test in tests::calyx_ffi_generated_wrappers::CALYX_FFI_TESTS { + test(ffi); + } + } +} +#[rustc_main] +#[coverage(off)] +pub fn main() -> () { + extern crate test; + test::test_main_static(&[&test_add, &test_sub]) +} diff --git a/tools/calyx-ffi/tests/adder.futil b/tools/calyx-ffi/tests/adder.futil new file mode 100644 index 000000000..1730a7c5f --- /dev/null +++ b/tools/calyx-ffi/tests/adder.futil @@ -0,0 +1,23 @@ +import "primitives/core.futil"; + +component main(lhs: 64, rhs: 64) -> (result: 64) { + cells { + adder = std_add(64); + temp = std_reg(64); + } + wires { + group add { + adder.left = lhs; + adder.right = rhs; + temp.in = adder.out; + temp.write_en = 1'b1; + add[done] = temp.done; + } + result = temp.out; + } + control { + add; + } +} + + diff --git a/tools/calyx-ffi/tests/arith_fuzz.rs b/tools/calyx-ffi/tests/arith_fuzz.rs new file mode 100644 index 000000000..63e896bd6 --- /dev/null +++ b/tools/calyx-ffi/tests/arith_fuzz.rs @@ -0,0 +1,74 @@ +use calyx_ffi::prelude::*; + +use calyx_ffi::cider_ffi_backend; + +// not necessary, just to show it off +calyx_ffi::declare_interface! { + In2Out1(lhs: 64, rhs: 64) -> (result: 64) +} + +#[calyx_ffi( + src = "tests/adder.futil", + comp = "main", + backend = cider_ffi_backend, + derive = [ + In2Out1(lhs: 64, rhs: 64) -> (result: 64) + ] +)] +struct Adder; + +#[calyx_ffi( + src = "tests/subber.futil", + comp = "main", + backend = cider_ffi_backend, + derive = [ + In2Out1(lhs: 64, rhs: 64) -> (result: 64) + ] +)] +struct Subber; + +#[cfg(test)] +#[calyx_ffi_tests] +mod tests { + use std::mem; + + use super::*; + use rand::Rng; + + // inv: the left argument will always be greater than the right + fn fuzz_in2out1 u64>( + comp: &mut I, + oracle: &F, + ) { + comp.reset(); + let mut rng = rand::thread_rng(); + for (mut x, mut y) in (0..100).map(|_| (rng.gen(), rng.gen())) { + if y > x { + mem::swap(&mut x, &mut y); + } + comp.set_lhs(x); + comp.set_rhs(y); + comp.go(); + assert_eq!( + oracle(x, y), + comp.result(), + "component did not evaluate f({}, {}) = {} correctly", + x, + y, + oracle(x, y) + ); + } + } + + #[calyx_ffi_test] + fn test_add(adder: &mut Adder) { + println!("testing adder"); + fuzz_in2out1(adder, &|x, y| x.wrapping_add(y)); + } + + #[calyx_ffi_test] + fn test_sub(subber: &mut Subber) { + println!("testing subber"); + fuzz_in2out1(subber, &|x, y| x - y); + } +} diff --git a/tools/calyx-ffi/tests/fifo.futil b/tools/calyx-ffi/tests/fifo.futil new file mode 100644 index 000000000..6bc65ba10 --- /dev/null +++ b/tools/calyx-ffi/tests/fifo.futil @@ -0,0 +1,130 @@ +import "primitives/core.futil"; +import "primitives/memories/seq.futil"; +import "primitives/binary_operators.futil"; +component fifo(cmd: 1, value: 32) -> () { + cells { + mem = seq_mem_d1(32, 16, 4); + reg_1 = std_reg(4); + reg_2 = std_reg(4); + ref ans = std_reg(32); + ref err = std_reg(1); + reg_3 = std_reg(5); + eq_4 = std_eq(5); + reg_2_incr_1_5 = std_add(4); + reg_3_decr_1_6 = std_sub(5); + eq_7 = std_eq(5); + reg_1_incr_1_8 = std_add(4); + reg_3_incr_1_9 = std_add(5); + cmd_eq_0_10 = std_eq(1); + cmd_eq_1_11 = std_eq(1); + } + wires { + group raise_err { + err.in = 1'd1; + err.write_en = 1'd1; + raise_err[done] = err.done; + } + comb group eq_4_group { + eq_4.left = reg_3.out; + eq_4.right = 5'd0; + } + group read_payload_from_mem_pop { + mem.addr0 = reg_2.out; + mem.content_en = 1'd1; + ans.write_en = mem.done ? 1'd1; + ans.in = mem.done ? mem.read_data; + read_payload_from_mem_pop[done] = ans.done; + } + group reg_2_incr_1_5_group { + reg_2_incr_1_5.left = reg_2.out; + reg_2_incr_1_5.right = 4'd1; + reg_2.write_en = 1'd1; + reg_2.in = reg_2_incr_1_5.out; + reg_2_incr_1_5_group[done] = reg_2.done; + } + group reg_3_decr_1_6_group { + reg_3_decr_1_6.left = reg_3.out; + reg_3_decr_1_6.right = 5'd1; + reg_3.write_en = 1'd1; + reg_3.in = reg_3_decr_1_6.out; + reg_3_decr_1_6_group[done] = reg_3.done; + } + comb group eq_7_group { + eq_7.left = reg_3.out; + eq_7.right = 5'd16; + } + group write_payload_to_mem { + mem.addr0 = reg_1.out; + mem.write_en = 1'd1; + mem.write_data = value; + write_payload_to_mem[done] = mem.done; + mem.content_en = 1'd1; + } + group reg_1_incr_1_8_group { + reg_1_incr_1_8.left = reg_1.out; + reg_1_incr_1_8.right = 4'd1; + reg_1.write_en = 1'd1; + reg_1.in = reg_1_incr_1_8.out; + reg_1_incr_1_8_group[done] = reg_1.done; + } + group reg_3_incr_1_9_group { + reg_3_incr_1_9.left = reg_3.out; + reg_3_incr_1_9.right = 5'd1; + reg_3.write_en = 1'd1; + reg_3.in = reg_3_incr_1_9.out; + reg_3_incr_1_9_group[done] = reg_3.done; + } + cmd_eq_0_10.left = cmd; + cmd_eq_0_10.right = 1'd0; + cmd_eq_1_11.left = cmd; + cmd_eq_1_11.right = 1'd1; + } + control { + par { + if cmd_eq_0_10.out { + if eq_4.out with eq_4_group { + raise_err; + } else { + seq { + read_payload_from_mem_pop; + reg_2_incr_1_5_group; + reg_3_decr_1_6_group; + } + } + } + if cmd_eq_1_11.out { + if eq_7.out with eq_7_group { + raise_err; + } else { + seq { + write_payload_to_mem; + reg_1_incr_1_8_group; + reg_3_incr_1_9_group; + } + } + } + } + } +} +component main(cmd: 1, value: 32) -> (ans: 32, err: 1) { + cells { + ans_reg = std_reg(32); + err_reg = std_reg(1); + queue = fifo(); + } + wires { + ans = ans_reg.out; + err = err_reg.out; + group dummy { + ans_reg.in = ans_reg.out; + ans_reg.write_en = 1'b1; + dummy[done] = ans_reg.done; + } + } + control { + seq { + invoke queue[ans = ans_reg, err = err_reg](cmd = cmd, value = value)(); + dummy; + } + } +} diff --git a/tools/calyx-ffi/tests/fifo.rs b/tools/calyx-ffi/tests/fifo.rs new file mode 100644 index 000000000..f1199f540 --- /dev/null +++ b/tools/calyx-ffi/tests/fifo.rs @@ -0,0 +1,69 @@ +use calyx_ffi::cider_ffi_backend; +use calyx_ffi::prelude::*; + +enum QueueCommand { + Pop = 0, + Push = 1, +} + +#[derive(PartialEq, Eq, Debug)] +enum QueueStatus { + Ok = 0, + Err = 1, +} + +calyx_ffi::declare_interface! { + Queue(cmd: 1, value: 32) -> (ans: 32, err: 1) impl { + fn status(&self) -> QueueStatus { + if self.err() == 0 { QueueStatus::Ok } else { QueueStatus::Err } + } + } mut impl { + fn assert_no_error(&mut self) { + assert_eq!(QueueStatus::Ok, self.status(), "queue underflowed or overflowed"); + } + + fn push(&mut self, value: u32) { + self.reset(); + self.set_cmd(QueueCommand::Push as u64); + self.set_value(value as u64); + self.go(); + self.assert_no_error(); + } + + fn pop(&mut self) -> u32 { + self.reset(); + self.set_cmd(QueueCommand::Pop as u64); + self.go(); + self.assert_no_error(); + self.ans() as u32 + } + } +} + +#[calyx_ffi( + src = "tests/fifo.futil", + comp = "main", + backend = cider_ffi_backend, + derive = [ + Queue(cmd: 1, value: 32) -> (ans: 32, err: 1) + ] +)] +struct Fifo; + +#[cfg(test)] +#[calyx_ffi_tests] +mod tests { + use super::*; + + #[calyx_ffi_test] + fn test_fifo(fifo: &mut Fifo) { + println!("testing fifo"); + + fifo.push(1); + fifo.push(2); + assert_eq!(1, fifo.pop()); + fifo.push(3); + assert_eq!(2, fifo.pop()); + assert_eq!(3, fifo.pop()); + } +} diff --git a/tools/calyx-ffi/tests/stack.futil b/tools/calyx-ffi/tests/stack.futil new file mode 100644 index 000000000..11575c4c0 --- /dev/null +++ b/tools/calyx-ffi/tests/stack.futil @@ -0,0 +1,70 @@ +import "primitives/core.futil"; +import "primitives/memories/seq.futil"; +import "primitives/binary_operators.futil"; + +component main(cmd: 1, value: 32) -> (out: 32, length: 4) { + cells { + store = seq_mem_d1(32, 16, 4); + next = std_reg( 4); + incr = std_add( 4); + decr = std_sub( 4); + last = std_reg(32); + test = std_eq(1); + } + wires { + out = last.out; + length = next.out; + + comb group check_cmd { + test.left = cmd; + test.right = 1'b0; + } + + group write_at_next { + store.addr0 = next.out; + store.write_data = value; + store.write_en = 1'b1; + store.content_en = 1'b1; + write_at_next[done] = store.done; + } + group read_from_next { + store.addr0 = next.out; + store.content_en = 1'b1; + last.in = store.read_data; + last.write_en = store.done; + read_from_next[done] = last.done; + } + group increment_next { + incr.left = next.out; + incr.right = 4'd1; + next.in = incr.out; + next.write_en = 1'b1; + increment_next[done] = next.done; + } + group decrement_next { + decr.left = next.out; + decr.right = 4'd1; + next.in = decr.out; + next.write_en = 1'b1; + decrement_next[done] = next.done; + } + } + control { + // if-else is buggy in cider2 + par { + if test.out with check_cmd { + seq { + write_at_next; + increment_next; + } + } + if cmd { + seq { + decrement_next; + read_from_next; + } + } + } + } +} + diff --git a/tools/calyx-ffi/tests/stack.rs b/tools/calyx-ffi/tests/stack.rs new file mode 100644 index 000000000..8e5b0d60a --- /dev/null +++ b/tools/calyx-ffi/tests/stack.rs @@ -0,0 +1,61 @@ +use calyx_ffi::cider_ffi_backend; +use calyx_ffi::prelude::*; + +enum StackCommand { + Push = 0, + Pop = 1, +} + +const STACK_CAPACITY: u64 = 16; + +calyx_ffi::declare_interface! { + Stack(cmd: 1, value: 32) -> (out: 32, length: 4) mut impl { + fn push(&mut self, value: u32,) { + assert!(self.length() < STACK_CAPACITY, "tried to push when length={}", STACK_CAPACITY); + println!("stack has length {} before push", self.length()); + let old_length = self.length(); + self.set_cmd(StackCommand::Push as u64); + self.set_value(value as u64); + self.go(); + assert_eq!(old_length + 1, self.length(), "stack length should increase by 1 on push"); + } + + fn pop(&mut self) -> u32 { + assert!(self.length() > 0, "tried to pop when stack empty"); + println!("stack has length {} before pop", self.length()); + let old_length = self.length(); + self.set_cmd(StackCommand::Pop as u64); + self.go(); + assert_eq!(old_length - 1, self.length(), "stack length should decrease by 1 on pop"); + self.out() as u32 + } + } +} + +#[calyx_ffi( + src = "tests/stack.futil", + comp = "main", + backend = cider_ffi_backend, + derive = [ + Stack(cmd: 1, value: 32) -> (out: 32, length: 4) + ] +)] +struct ReallyBadStack; + +#[cfg(test)] +#[calyx_ffi_tests] +mod tests { + use super::*; + + #[calyx_ffi_test] + fn test_stack(stack: &mut ReallyBadStack) { + println!("testing stack"); + + stack.push(1); + stack.push(2); + assert_eq!(2, stack.pop()); + stack.push(3); + assert_eq!(3, stack.pop()); + assert_eq!(1, stack.pop()); + } +} diff --git a/tools/calyx-ffi/tests/subber.futil b/tools/calyx-ffi/tests/subber.futil new file mode 100644 index 000000000..260456f93 --- /dev/null +++ b/tools/calyx-ffi/tests/subber.futil @@ -0,0 +1,21 @@ +import "primitives/core.futil"; + +component main(lhs: 64, rhs: 64) -> (result: 64) { + cells { + subber = std_sub(64); + temp = std_reg(64); + } + wires { + group sub { + subber.left = lhs; + subber.right = rhs; + temp.in = subber.out; + temp.write_en = 1'b1; + sub[done] = temp.done; + } + result = temp.out; + } + control { + sub; + } +}