From 015da8096df2a4d34eb211908c9062c4b19688d2 Mon Sep 17 00:00:00 2001 From: calebmkim <55243755+calebmkim@users.noreply.github.com> Date: Mon, 13 May 2024 08:26:18 -0400 Subject: [PATCH] Add One-Hot Encoding for Static FSMs (#2037) * cli options * static control tests * fsm implementation type * FSM allocation * extended instatiation of staticfsm object with one hot encoding * one hot * clippy * clippy * does this make clippy stop complainng * one-hot initialize to 00001 * cleaner code * loose ends * code refactoring * hopefully github shows fewer changes * clippy * rewrite tests * documentation, code cleaning * better documentation * higher one-hot cutoff --------- Co-authored-by: Parth Sarkar --- calyx-opt/src/analysis/static_schedule.rs | 347 +++++++++++++----- calyx-opt/src/passes/compile_static.rs | 213 ++++++----- examples/futil/dot-product.expect | 4 +- examples/futil/simple.expect | 2 +- examples/futil/vectorized-add.expect | 4 +- primitives/compile.futil | 29 ++ primitives/core.futil | 1 - primitives/core.sv | 10 - runt.toml | 37 ++ tests/backend/verilog/big-const.expect | 31 ++ tests/backend/verilog/data-instance.expect | 31 ++ .../memory-with-external-attribute.expect | 41 ++- tests/import/a.expect | 15 +- .../compile-static-interface-one-cycle.expect | 2 +- .../compile-static-interface-repeat.expect | 9 +- .../compile-static-interface.expect | 7 +- .../interface-one-hot.expect | 102 +++++ .../interface-one-hot.futil | 45 +++ .../compile-static/interface-and-cs.expect | 9 +- tests/passes/compile-static/one-hot.expect | 76 ++++ tests/passes/compile-static/one-hot.futil | 32 ++ .../compile-static/rewrite-group-go.expect | 4 +- .../rewrite-static-while-nested.expect | 4 +- .../rewrite-static-while.expect | 4 +- .../compile-static/separate-fsms.expect | 10 +- 25 files changed, 850 insertions(+), 219 deletions(-) create mode 100644 tests/passes/compile-static-interface/interface-one-hot.expect create mode 100644 tests/passes/compile-static-interface/interface-one-hot.futil create mode 100644 tests/passes/compile-static/one-hot.expect create mode 100644 tests/passes/compile-static/one-hot.futil diff --git a/calyx-opt/src/analysis/static_schedule.rs b/calyx-opt/src/analysis/static_schedule.rs index fca632031b..2630ee74a4 100644 --- a/calyx-opt/src/analysis/static_schedule.rs +++ b/calyx-opt/src/analysis/static_schedule.rs @@ -4,44 +4,94 @@ use calyx_ir::{build_assignments, Nothing}; use calyx_ir::{guard, structure}; use ir::Guard; use itertools::Itertools; -use std::collections::{HashSet, VecDeque}; +use std::collections::{HashMap, HashSet, VecDeque}; +use std::ops::Not; use std::rc::Rc; #[derive(Debug, Clone, Copy)] -// Define an enum called Fruit +// Define an FSMEncoding Enum enum FSMEncoding { Binary, - _OneHot, + OneHot, +} + +#[derive(Debug)] +enum FSMImplementationSpec { + Single, + // How many duplicates + _Duplicate(u64), + // How many times to split + _Split(u64), +} + +#[derive(Debug)] +// Define an enum called FSMType +enum FSMImplementation { + // Default option: just a single register + Single(ir::RRC), + // Duplicate the register to reduce fanout when querying + // (all FSMs in this vec still have all of the states) + _Duplicate(Vec>), + // Split the FSM to reduce fanout when querying. + // (the FSMs partition the states exactly). + // Each FSM has fewer bits but I suspect the logic might be more complicated. + _Split(Vec>), +} + +impl FSMImplementation { + fn get_single_cell(&self) -> ir::RRC { + match self { + FSMImplementation::Single(cell) => Rc::clone(cell), + _ => unreachable!( + "called `get_single_cell()` on non-single FSM implementation " + ), + } + } } #[derive(Debug)] pub struct StaticFSM { _num_states: u64, - _encoding: FSMEncoding, + encoding: FSMEncoding, // The fsm's bitwidth (this redundant information bc we have `cell`) // but makes it easier if we easily have access to this. bitwidth: u64, - // The actual register - cell: ir::RRC, + // The actual register(s) used to implement the FSM + implementation: FSMImplementation, + // Mapping of queries from (u64, u64) -> Port + queries: HashMap<(u64, u64), ir::RRC>, } impl StaticFSM { // Builds a static_fsm from: num_states and encoding type. fn from_basic_info( num_states: u64, encoding: FSMEncoding, + _implementation: FSMImplementationSpec, builder: &mut ir::Builder, ) -> Self { - // Only support Binary encoding currently. - assert!(matches!(encoding, FSMEncoding::Binary)); - // First build the fsm we will use to realize the schedule. - let fsm_size = - get_bit_width_from(num_states + 1 /* represent 0..latency */); - let fsm = builder.add_primitive("fsm", "std_reg", &[fsm_size]); + // Determine number of bits needed in the register. + let fsm_size = match encoding { + /* represent 0..latency */ + FSMEncoding::Binary => get_bit_width_from(num_states + 1), + FSMEncoding::OneHot => num_states, + }; + // OHE needs an initial value of 1. + let register = match encoding { + FSMEncoding::Binary => { + builder.add_primitive("fsm", "std_reg", &[fsm_size]) + } + FSMEncoding::OneHot => { + builder.add_primitive("fsm", "init_one_reg", &[fsm_size]) + } + }; + let fsm = FSMImplementation::Single(register); + StaticFSM { _num_states: num_states, - _encoding: encoding, + encoding, bitwidth: fsm_size, - cell: fsm, + implementation: fsm, + queries: HashMap::new(), } } @@ -54,72 +104,111 @@ impl StaticFSM { // (NOTE: if the guard is true while we are counting up we will just // ignore that guard and keep on counting-- we don't reset or anything. // The guard is just there to make sure we only go from 0->1 when appropriate.) + // (IMPORTANT WEIRD PRECONDITION): if `incr_cond` is Some(_), we assume n > 0. pub fn count_to_n( - &self, + &mut self, builder: &mut ir::Builder, n: u64, incr_condition: Option>, ) -> Vec> { - // Only support Binary encoding currently. - assert!(matches!(self._encoding, FSMEncoding::Binary)); - // Add assignments to increment the fsm by one unconditionally. - structure!( builder; - // done hole will be undefined bc of early reset - let signal_on = constant(1,1); - let adder = prim std_add(self.bitwidth); - let const_one = constant(1, self.bitwidth); - let first_state = constant(0, self.bitwidth); - let final_state = constant(n, self.bitwidth); - ); - let fsm_cell = Rc::clone(&self.cell); - let final_state_guard: ir::Guard = - guard!(fsm_cell["out"] == final_state["out"]); - match incr_condition { - None => { - // "Normal" logic to increment FSM by one. - let not_final_state_guard: ir::Guard = - guard!(fsm_cell["out"] != final_state["out"]); - build_assignments!( - builder; - // increments the fsm - adder["left"] = ? fsm_cell["out"]; - adder["right"] = ? const_one["out"]; - fsm_cell["write_en"] = ? signal_on["out"]; - fsm_cell["in"] = not_final_state_guard ? adder["out"]; - // resets the fsm early - fsm_cell["in"] = final_state_guard ? first_state["out"]; - ) - .to_vec() - } - Some(condition_guard) => { - let first_state_guard: ir::Guard = - guard!(fsm_cell["out"] == first_state["out"]); - let cond_and_first_state = - ir::Guard::and(condition_guard, first_state_guard); - let not_first_state: ir::Guard = - guard!(fsm_cell["out"] != first_state["out"]); - let not_last_state: ir::Guard = - guard!(fsm_cell["out"] != final_state["out"]); - let in_between_guard = - ir::Guard::and(not_first_state, not_last_state); - build_assignments!( - builder; - // Incrementsthe fsm - adder["left"] = ? fsm_cell["out"]; - adder["right"] = ? const_one["out"]; - // Always write into fsm. - fsm_cell["write_en"] = ? signal_on["out"]; - // If fsm == 0 and cond is high, then we start an execution. - fsm_cell["in"] = cond_and_first_state ? const_one["out"]; - // If 1 < fsm < n - 1, then we unconditionally increment the fsm. - fsm_cell["in"] = in_between_guard ? adder["out"]; - // If fsm == n -1 , then we reset the FSM. - fsm_cell["in"] = final_state_guard ? first_state["out"]; - // Otherwise the FSM is not assigned to, so it defaults to 0. - // If we want, we could add an explicit assignment here that sets it - // to zero. - ) - .to_vec() + { + assert!(matches!( + self.implementation, + FSMImplementation::Single(_) + )); + let fsm_cell: Rc> = + self.implementation.get_single_cell(); + // For OHE, the "adder" can just be a shifter. + // For OHE the first_state = 1 rather than 0. + // Final state is encoded differently for OHE vs. Binary + let (adder, first_state, final_state_guard) = match self.encoding { + FSMEncoding::Binary => ( + builder.add_primitive("adder", "std_add", &[self.bitwidth]), + builder.add_constant(0, self.bitwidth), + { + let const_n = builder.add_constant(n, self.bitwidth); + let g = guard!(fsm_cell["out"] == const_n["out"]); + g + }, + ), + FSMEncoding::OneHot => ( + builder.add_primitive("lsh", "std_lsh", &[self.bitwidth]), + builder.add_constant(1, self.bitwidth), + self.get_one_hot_query( + Rc::clone(&fsm_cell), + (n, n + 1), + builder, + ), + ), + }; + structure!( builder; + let signal_on = constant(1,1); + let const_one = constant(1, self.bitwidth); + ); + let not_final_state_guard = + ir::Guard::Not(Box::new(final_state_guard.clone())); + match incr_condition { + None => { + // Unconditionally increment FSM. + build_assignments!( + builder; + // increments the fsm + adder["left"] = ? fsm_cell["out"]; + adder["right"] = ? const_one["out"]; + fsm_cell["write_en"] = ? signal_on["out"]; + fsm_cell["in"] = not_final_state_guard ? adder["out"]; + // resets the fsm early + fsm_cell["in"] = final_state_guard ? first_state["out"]; + ) + .to_vec() + } + Some(condition_guard) => { + // Only start incrementing when FSM == first_state and + // conditiona_guard is true. + // After that, we can unconditionally increment. + let first_state_guard = match self.encoding { + FSMEncoding::Binary => { + let g = + guard!(fsm_cell["out"] == first_state["out"]); + g + } + // This is better than checking if FSM == first_state + // be this is only checking a single bit. + FSMEncoding::OneHot => self.get_one_hot_query( + Rc::clone(&fsm_cell), + (0, 1), + builder, + ), + }; + let not_first_state: ir::Guard = + ir::Guard::Not(Box::new(first_state_guard.clone())); + let cond_and_first_state = ir::Guard::and( + condition_guard.clone(), + first_state_guard.clone(), + ); + let not_cond_and_first_state = + ir::Guard::not(condition_guard.clone()) + .and(first_state_guard); + let in_between_guard = + ir::Guard::and(not_first_state, not_final_state_guard); + let my_assigns = build_assignments!( + builder; + // Incrementsthe fsm + adder["left"] = ? fsm_cell["out"]; + adder["right"] = ? const_one["out"]; + // Always write into fsm. + fsm_cell["write_en"] = ? signal_on["out"]; + // If fsm == first_state and cond is high, then we start an execution. + fsm_cell["in"] = cond_and_first_state ? adder["out"]; + // If first_state < fsm < n, then we unconditionally increment the fsm. + fsm_cell["in"] = in_between_guard ? adder["out"]; + // If fsm == n, then we reset the FSM. + fsm_cell["in"] = final_state_guard ? first_state["out"]; + // Otherwise we set the FSM equal to first_state. + fsm_cell["in"] = not_cond_and_first_state ? first_state["out"]; + ); + my_assigns.to_vec() + } } } } @@ -127,14 +216,21 @@ impl StaticFSM { // Returns a guard that takes a (beg, end) `query`, and returns the equivalent // guard to `beg <= fsm.out < end`. pub fn query_between( - &self, + &mut self, builder: &mut ir::Builder, query: (u64, u64), ) -> Box> { - // Only support Binary encoding currently. - assert!(matches!(self._encoding, FSMEncoding::Binary)); + assert!(matches!(self.implementation, FSMImplementation::Single(_))); + let (beg, end) = query; - let fsm_cell = Rc::clone(&self.cell); + if matches!(self.encoding, FSMEncoding::OneHot) { + // Querying OHE is easy, since we already have `self.get_one_hot_query()` + let fsm_cell = self.implementation.get_single_cell(); + let g = self.get_one_hot_query(fsm_cell, (beg, end), builder); + return Box::new(g); + } + + let fsm_cell = self.implementation.get_single_cell(); if beg + 1 == end { // if beg + 1 == end then we only need to check if fsm == beg let interval_const = builder.add_constant(beg, self.bitwidth); @@ -158,14 +254,82 @@ impl StaticFSM { } } + // Given a one-hot query, it will return a guard corresponding to that query. + // If it has already built the query (i.e., added the wires/continuous assigments), + // it just uses the same port. + // Otherwise it will build the query. + fn get_one_hot_query( + &mut self, + fsm_cell: ir::RRC, + (lb, ub): (u64, u64), + builder: &mut ir::Builder, + ) -> ir::Guard { + match self.queries.get(&(lb, ub)) { + None => { + let port = Self::build_one_hot_query( + Rc::clone(&fsm_cell), + self.bitwidth, + (lb, ub), + builder, + ); + self.queries.insert((lb, ub), Rc::clone(&port)); + ir::Guard::port(port) + } + Some(port) => ir::Guard::port(Rc::clone(port)), + } + } + + // Given a (lb, ub) query, and an fsm (and for convenience, a bitwidth), + // Returns a `port`: port is a `wire.out`, where `wire` holds + // whether or not the query is true, i.e., whether the FSM really is + // between [lb, ub). + fn build_one_hot_query( + fsm_cell: ir::RRC, + fsm_bitwidth: u64, + (lb, ub): (u64, u64), + builder: &mut ir::Builder, + ) -> ir::RRC { + // The wire that holds the query + let formatted_name = format!("bw_{}_{}", lb, ub); + let wire: ir::RRC = + builder.add_primitive(formatted_name, "std_wire", &[1]); + let wire_out = wire.borrow().get("out"); + + // Continuous assignments to check the FSM + let assigns = { + let in_width = fsm_bitwidth; + // Since 00...00 is the initial state, we need to check lb-1. + let start_index = lb; + // Since verilog slices are inclusive. + let end_index = ub - 1; + let out_width = ub - lb; // == (end_index - start_index + 1) + structure!(builder; + let slicer = prim std_bit_slice(in_width, start_index, end_index, out_width); + let const_slice_0 = constant(0, out_width); + let signal_on = constant(1,1); + ); + let slicer_neq_0 = guard!(slicer["out"] != const_slice_0["out"]); + // Extend the continuous assignmments to include this particular query for FSM state; + let my_assigns = build_assignments!(builder; + slicer["in"] = ? fsm_cell["out"]; + wire["in"] = slicer_neq_0 ? signal_on["out"]; + ); + my_assigns.to_vec() + }; + builder.add_continuous_assignments(assigns); + wire_out + } + // Return a unique id (i.e., get_unique_id for each FSM in the same component // will be different). pub fn get_unique_id(&self) -> ir::Id { - self.cell.borrow().name() + assert!(matches!(self.implementation, FSMImplementation::Single(_))); + self.implementation.get_single_cell().borrow().name() } // Return the bitwidth of an FSM object pub fn get_bitwidth(&self) -> u64 { + assert!(matches!(self.implementation, FSMImplementation::Single(_))); self.bitwidth } } @@ -250,11 +414,20 @@ impl StaticSchedule { &mut self, builder: &mut ir::Builder, static_component_interface: bool, + one_hot_cutoff: u64, ) -> (VecDeque>>, StaticFSM) { + // Choose encoding based on one-hot cutoff. + let encoding = if self.num_states > one_hot_cutoff { + FSMEncoding::Binary + } else { + FSMEncoding::OneHot + }; + // First build the fsm we will use to realize the schedule. - let fsm_object = StaticFSM::from_basic_info( + let mut fsm_object = StaticFSM::from_basic_info( self.num_states, - FSMEncoding::Binary, + encoding, + FSMImplementationSpec::Single, builder, ); @@ -286,7 +459,11 @@ impl StaticSchedule { let mut assigns: Vec> = static_assigns .into_iter() .map(|static_assign| { - Self::make_assign_dyn(static_assign, &fsm_object, builder) + Self::make_assign_dyn( + static_assign, + &mut fsm_object, + builder, + ) }) .collect(); // For static components, we don't unconditionally start counting. @@ -317,7 +494,7 @@ impl StaticSchedule { // is_static_comp is necessary becasue it ... fn make_guard_dyn( guard: ir::Guard, - fsm_object: &StaticFSM, + fsm_object: &mut StaticFSM, builder: &mut ir::Builder, ) -> Box> { match guard { @@ -347,7 +524,7 @@ impl StaticSchedule { // Mainly transforms the guards from %[2:3] -> fsm.out >= 2 & fsm.out <= 3 fn make_assign_dyn( assign: ir::Assignment, - fsm_object: &StaticFSM, + fsm_object: &mut StaticFSM, builder: &mut ir::Builder, ) -> ir::Assignment { ir::Assignment { diff --git a/calyx-opt/src/passes/compile_static.rs b/calyx-opt/src/passes/compile_static.rs index 87d676a0a3..45ce1ca3aa 100644 --- a/calyx-opt/src/passes/compile_static.rs +++ b/calyx-opt/src/passes/compile_static.rs @@ -1,15 +1,16 @@ use crate::analysis::{GraphColoring, StaticFSM, StaticSchedule}; -use crate::traversal::{Action, Named, VisResult, Visitor}; +use crate::traversal::{ + Action, ConstructVisitor, Named, ParseVal, PassOpt, VisResult, Visitor, +}; use calyx_ir as ir; use calyx_ir::{guard, structure, GetAttributes}; -use calyx_utils::Error; +use calyx_utils::{CalyxResult, Error}; use ir::{build_assignments, RRC}; use itertools::Itertools; use std::collections::{HashMap, HashSet}; use std::ops::Not; use std::rc::Rc; -#[derive(Default)] /// Compiles Static Islands pub struct CompileStatic { /// maps original static group names to the corresponding group that has an FSM that reset early @@ -20,6 +21,10 @@ pub struct CompileStatic { signal_reg_map: HashMap, /// maps reset_early_group names to StaticFSM object fsm_info_map: HashMap>, + // ========= Pass Options ============ + /// How many states the static FSM must have before we pick binary encoding over + /// one-hot + one_hot_cutoff: u64, } impl Named for CompileStatic { @@ -30,39 +35,37 @@ impl Named for CompileStatic { fn description() -> &'static str { "compiles static sub-programs into a dynamic group" } -} -// Given a list of `static_groups`, find the group named `name`. -// If there is no such group, then there is an unreachable! error. -fn find_static_group( - name: &ir::Id, - static_groups: &[ir::RRC], -) -> ir::RRC { - Rc::clone( - static_groups - .iter() - .find(|static_group| static_group.borrow().name() == name) - .unwrap_or_else(|| { - unreachable!("couldn't find static group {name}") - }), - ) + fn opts() -> Vec { + vec![PassOpt::new( + "one-hot-cutoff", + "The upper limit on the number of states the static FSM must have before we pick binary \ + encoding over one-hot. Defaults to 0 (i.e., always choose binary encoding)", + ParseVal::Num(0), + PassOpt::parse_num, + )] + } } -// Given an input static_group `sgroup`, finds the names of all of the groups -// that it triggers through their go hole. -// E.g., if `sgroup` has assignments that write to `sgroup1[go]` and `sgroup2[go]` -// then return `{sgroup1, sgroup2}` -// Assumes that static groups will only write the go holes of other static -// groups, and never dynamic groups (which seems like a reasonable assumption). -fn get_go_writes(sgroup: &ir::RRC) -> HashSet { - let mut uses = HashSet::new(); - for asgn in &sgroup.borrow().assignments { - let dst = asgn.dst.borrow(); - if dst.is_hole() && dst.name == "go" { - uses.insert(dst.get_parent_name()); - } +impl ConstructVisitor for CompileStatic { + fn from(ctx: &ir::Context) -> CalyxResult { + let opts = Self::get_opts(ctx); + + Ok(CompileStatic { + one_hot_cutoff: opts["one-hot-cutoff"].pos_num().unwrap(), + reset_early_map: HashMap::new(), + wrapper_map: HashMap::new(), + signal_reg_map: HashMap::new(), + fsm_info_map: HashMap::new(), + }) + } + + fn clear_data(&mut self) { + self.reset_early_map = HashMap::new(); + self.wrapper_map = HashMap::new(); + self.signal_reg_map = HashMap::new(); + self.fsm_info_map = HashMap::new(); } - uses } impl CompileStatic { @@ -92,7 +95,8 @@ impl CompileStatic { }); // fsm.out == 0 - let first_state = *fsm_object.borrow().query_between(builder, (0, 1)); + let first_state = + *fsm_object.borrow_mut().query_between(builder, (0, 1)); structure!( builder; let signal_on = constant(1, 1); let signal_off = constant(0, 1); @@ -163,7 +167,7 @@ impl CompileStatic { ) }); - let fsm_eq_0 = *fsm_object.borrow().query_between(builder, (0, 1)); + let fsm_eq_0 = *fsm_object.borrow_mut().query_between(builder, (0, 1)); let wrapper_group = builder.add_group(format!("while_wrapper_{}", group_name)); @@ -189,34 +193,6 @@ impl CompileStatic { wrapper_group } - // Get early reset group name from static control (we assume the static control - // is an enable). - fn get_reset_group_name(&self, sc: &mut ir::StaticControl) -> &ir::Id { - // assume that there are only static enables left. - // if there are any other type of static control, then error out. - let ir::StaticControl::Enable(s) = sc else { - unreachable!("Non-Enable Static Control should have been compiled away. Run {} to do this", crate::passes::StaticInliner::name()); - }; - - let sgroup = s.group.borrow_mut(); - let sgroup_name = sgroup.name(); - // get the "early reset group". It should exist, since we made an - // early_reset group for every static group in the component - let early_reset_name = - self.reset_early_map.get(&sgroup_name).unwrap_or_else(|| { - unreachable!( - "group {} not in self.reset_early_map", - sgroup_name - ) - }); - - early_reset_name - } -} - -// These are the functions/methods used to assign FSMs to static islands -// (Currently we use greedy coloring). -impl CompileStatic { // Given a `coloring` of static group names, along with the actual `static_groups`, // it builds one StaticSchedule per color. fn build_schedule_objects( @@ -252,6 +228,66 @@ impl CompileStatic { .collect() } + // Get early reset group name from static control (we assume the static control + // is an enable). + fn get_reset_group_name(&self, sc: &mut ir::StaticControl) -> &ir::Id { + // assume that there are only static enables left. + // if there are any other type of static control, then error out. + let ir::StaticControl::Enable(s) = sc else { + unreachable!("Non-Enable Static Control should have been compiled away. Run {} to do this", crate::passes::StaticInliner::name()); + }; + + let sgroup = s.group.borrow_mut(); + let sgroup_name = sgroup.name(); + // get the "early reset group". It should exist, since we made an + // early_reset group for every static group in the component + let early_reset_name = + self.reset_early_map.get(&sgroup_name).unwrap_or_else(|| { + unreachable!( + "group {} not in self.reset_early_map", + sgroup_name + ) + }); + + early_reset_name + } +} + +// These are the functions used to allocate FSMs to static islands +impl CompileStatic { + // Given a list of `static_groups`, find the group named `name`. + // If there is no such group, then there is an unreachable! error. + fn find_static_group( + name: &ir::Id, + static_groups: &[ir::RRC], + ) -> ir::RRC { + Rc::clone( + static_groups + .iter() + .find(|static_group| static_group.borrow().name() == name) + .unwrap_or_else(|| { + unreachable!("couldn't find static group {name}") + }), + ) + } + + // Given an input static_group `sgroup`, finds the names of all of the groups + // that it triggers through their go hole. + // E.g., if `sgroup` has assignments that write to `sgroup1[go]` and `sgroup2[go]` + // then return `{sgroup1, sgroup2}` + // Assumes that static groups will only write the go holes of other static + // groups, and never dynamic groups (which seems like a reasonable assumption). + fn get_go_writes(sgroup: &ir::RRC) -> HashSet { + let mut uses = HashSet::new(); + for asgn in &sgroup.borrow().assignments { + let dst = asgn.dst.borrow(); + if dst.is_hole() && dst.name == "go" { + uses.insert(dst.get_parent_name()); + } + } + uses + } + // Gets all of the triggered static groups within `c`, and adds it to `cur_names`. // Relies on sgroup_uses_map to take into account groups that are triggered through // their `go` hole. @@ -417,8 +453,10 @@ impl CompileStatic { group_names: &mut HashSet, sgroups: &Vec>, ) { - let group_uses = - get_go_writes(&find_static_group(parent_group, sgroups)); + let group_uses = Self::get_go_writes(&Self::find_static_group( + parent_group, + sgroups, + )); for group_use in group_uses { for ancestor in full_group_ancestry.iter() { cur_mapping.entry(*ancestor).or_default().insert(group_use); @@ -461,6 +499,21 @@ impl CompileStatic { } cur_mapping } + + pub fn get_coloring( + sgroups: &Vec>, + control: &ir::Control, + ) -> HashMap { + // `sgroup_uses_map` builds a mapping of static groups -> groups that + // it (even indirectly) triggers the `go` port of. + let sgroup_uses_map = Self::build_sgroup_uses_map(sgroups); + // Build conflict graph and get coloring. + let mut conflict_graph: GraphColoring = + GraphColoring::from(sgroups.iter().map(|g| g.borrow().name())); + Self::add_par_conflicts(control, &sgroup_uses_map, &mut conflict_graph); + Self::add_go_port_conflicts(&sgroup_uses_map, &mut conflict_graph); + conflict_graph.color_greedy(None, true) + } } // These are the functions used to compile for the static *component* interface @@ -519,7 +572,7 @@ impl CompileStatic { // Makes `done` signal for promoted static component. fn make_done_signal_for_promoted_component( - fsm: &StaticFSM, + fsm: &mut StaticFSM, builder: &mut ir::Builder, comp_sig: RRC, ) -> Vec> { @@ -581,6 +634,7 @@ impl CompileStatic { // The assignments are removed from `sgroup` and placed into // `builder.component`'s continuous assignments. fn compile_static_interface( + &self, sgroup: ir::RRC, builder: &mut ir::Builder, ) { @@ -588,7 +642,8 @@ impl CompileStatic { // Build a StaticSchedule object, realize it and add assignments // as continuous assignments. let mut sch = StaticSchedule::from(vec![Rc::clone(&sgroup)]); - let (mut assigns, fsm) = sch.realize_schedule(builder, true); + let (mut assigns, mut fsm) = + sch.realize_schedule(builder, true, self.one_hot_cutoff); builder .component .continuous_assignments @@ -598,7 +653,7 @@ impl CompileStatic { // If necessary, add the logic to produce a done signal. let done_assigns = Self::make_done_signal_for_promoted_component( - &fsm, builder, comp_sig, + &mut fsm, builder, comp_sig, ); builder .component @@ -666,20 +721,7 @@ impl Visitor for CompileStatic { // The first thing is to assign FSMs -> static islands. // We sometimes assign the same FSM to different static islands // to reduce register usage. We do this by getting greedy coloring. - - // `sgroup_uses_map` builds a mapping of static groups -> groups that - // it (even indirectly) triggers the `go` port of. - let sgroup_uses_map = Self::build_sgroup_uses_map(&sgroups); - // Build conflict graph and get coloring. - let mut conflict_graph: GraphColoring = - GraphColoring::from(sgroups.iter().map(|g| g.borrow().name())); - Self::add_par_conflicts( - &comp.control.borrow(), - &sgroup_uses_map, - &mut conflict_graph, - ); - Self::add_go_port_conflicts(&sgroup_uses_map, &mut conflict_graph); - let coloring = conflict_graph.color_greedy(None, true); + let coloring = Self::get_coloring(&sgroups, &comp.control.borrow()); let mut builder = ir::Builder::new(comp, sigs); // Build one StaticSchedule object per color @@ -706,13 +748,16 @@ impl Visitor for CompileStatic { // Compile top level static group differently. // We know that the top level static island has its own // unique FSM so we can do `.pop().unwrap()` - Self::compile_static_interface( + self.compile_static_interface( sch.static_groups.pop().unwrap(), &mut builder, ) } else { - let (mut static_group_assigns, fsm) = sch - .realize_schedule(&mut builder, static_component_interface); + let (mut static_group_assigns, fsm) = sch.realize_schedule( + &mut builder, + static_component_interface, + self.one_hot_cutoff, + ); let fsm_ref = ir::rrc(fsm); for static_group in sch.static_groups.iter() { // Create the dynamic "early reset group" that will replace the static group. diff --git a/examples/futil/dot-product.expect b/examples/futil/dot-product.expect index f9eaec7a73..023e819353 100644 --- a/examples/futil/dot-product.expect +++ b/examples/futil/dot-product.expect @@ -51,9 +51,9 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { fsm.write_en = early_reset_cond00_go.out | early_reset_static_seq_go.out ? 1'd1; fsm.clk = clk; fsm.reset = reset; - fsm.in = fsm.out != 4'd0 & early_reset_cond00_go.out ? adder.out; + fsm.in = !(fsm.out == 4'd0) & early_reset_cond00_go.out ? adder.out; fsm.in = fsm.out == 4'd0 & early_reset_cond00_go.out | fsm.out == 4'd7 & early_reset_static_seq_go.out ? 4'd0; - fsm.in = fsm.out != 4'd7 & early_reset_static_seq_go.out ? adder0.out; + fsm.in = !(fsm.out == 4'd7) & early_reset_static_seq_go.out ? adder0.out; adder.left = early_reset_cond00_go.out ? fsm.out; adder.right = early_reset_cond00_go.out ? 4'd1; add0.left = fsm.out == 4'd6 & early_reset_static_seq_go.out ? v0.read_data; diff --git a/examples/futil/simple.expect b/examples/futil/simple.expect index 1dd0c5ad00..f8e7a61ec9 100644 --- a/examples/futil/simple.expect +++ b/examples/futil/simple.expect @@ -17,7 +17,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { fsm.write_en = early_reset_static_seq_go.out ? 1'd1; fsm.clk = clk; fsm.reset = reset; - fsm.in = fsm.out != 3'd4 & early_reset_static_seq_go.out ? adder.out; + fsm.in = !(fsm.out == 3'd4) & early_reset_static_seq_go.out ? adder.out; fsm.in = fsm.out == 3'd4 & early_reset_static_seq_go.out ? 3'd0; adder.left = early_reset_static_seq_go.out ? fsm.out; adder.right = early_reset_static_seq_go.out ? 3'd1; diff --git a/examples/futil/vectorized-add.expect b/examples/futil/vectorized-add.expect index d8d8c4e602..93ac592370 100644 --- a/examples/futil/vectorized-add.expect +++ b/examples/futil/vectorized-add.expect @@ -48,8 +48,8 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { fsm.write_en = early_reset_cond00_go.out | early_reset_static_seq_go.out ? 1'd1; fsm.clk = clk; fsm.reset = reset; - fsm.in = fsm.out != 3'd0 & early_reset_cond00_go.out ? adder.out; - fsm.in = fsm.out != 3'd3 & early_reset_static_seq_go.out ? adder0.out; + fsm.in = !(fsm.out == 3'd0) & early_reset_cond00_go.out ? adder.out; + fsm.in = !(fsm.out == 3'd3) & early_reset_static_seq_go.out ? adder0.out; fsm.in = fsm.out == 3'd0 & early_reset_cond00_go.out | fsm.out == 3'd3 & early_reset_static_seq_go.out ? 3'd0; adder.left = early_reset_cond00_go.out ? fsm.out; adder.right = early_reset_cond00_go.out ? 3'd1; diff --git a/primitives/compile.futil b/primitives/compile.futil index 30b5eac062..b291df0c15 100644 --- a/primitives/compile.futil +++ b/primitives/compile.futil @@ -18,6 +18,12 @@ comb primitive std_add<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) assign out = left + right; } +// Shift operator. +comb primitive std_lsh<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: WIDTH) +{ + assign out = left << right; +} + /// Standard register with a one-cycle latency. // ANCHOR: std_reg_def primitive std_reg<"state_share"=1>[WIDTH]( @@ -41,3 +47,26 @@ primitive std_reg<"state_share"=1>[WIDTH]( end else done <= 1'd0; end } + +/// Standard register with a one-cycle latency. Only difference with std_reg +// is that starts at 1 rather than 0. This helps with one-hot encoding. +primitive init_one_reg<"state_share"=1>[WIDTH]( + @write_together(1) @data in: WIDTH, + @write_together(1) @interval(1) @go write_en: 1, + @clk clk: 1, + @reset reset: 1 +) -> ( + @stable out: WIDTH, + @done done: 1 +) +{ + always_ff @(posedge clk) begin + if (reset) begin + out <= 1; + done <= 0; + end else if (write_en) begin + out <= in; + done <= 1'd1; + end else done <= 1'd0; + end +} diff --git a/primitives/core.futil b/primitives/core.futil index 2f48fca680..99e5127f87 100644 --- a/primitives/core.futil +++ b/primitives/core.futil @@ -22,7 +22,6 @@ extern "core.sv" { comb primitive std_neq<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: 1); comb primitive std_ge<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: 1); comb primitive std_le<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: 1); - comb primitive std_lsh<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: WIDTH); comb primitive std_rsh<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: WIDTH); comb primitive std_mux<"share"=1>[WIDTH](@data cond: 1, @data tru: WIDTH, @data fal: WIDTH) -> (out: WIDTH); } diff --git a/primitives/core.sv b/primitives/core.sv index 7bcf11c2aa..227fd06046 100644 --- a/primitives/core.sv +++ b/primitives/core.sv @@ -183,16 +183,6 @@ module std_le #( assign out = left <= right; endmodule -module std_lsh #( - parameter WIDTH = 32 -) ( - input wire logic [WIDTH-1:0] left, - input wire logic [WIDTH-1:0] right, - output logic [WIDTH-1:0] out -); - assign out = left << right; -endmodule - module std_rsh #( parameter WIDTH = 32 ) ( diff --git a/runt.toml b/runt.toml index 8d5d42a070..740d4f1cb8 100644 --- a/runt.toml +++ b/runt.toml @@ -231,6 +231,24 @@ fud exec --from calyx --to jq \ """ timeout = 120 +[[tests]] +name = "correctness static timing one-hot encoding" +paths = [ + "tests/correctness/static-interface/*.futil", +] +cmd = """ +fud exec --from calyx --to jq \ + --through verilog \ + --through dat \ + -s calyx.exec './target/debug/calyx' \ + -s calyx.flags '-x compile-static:one-hot-cutoff=500' \ + -s verilog.cycle_limit 500 \ + -s verilog.data {}.data \ + -s jq.expr ".memories" \ + {} -q +""" +timeout = 120 + # Variants of the above but for `--nested` (non-ANF Verilog emission) mode. [[tests]] name = "correctness nested" @@ -268,6 +286,25 @@ fud exec --from calyx --to jq \ """ timeout = 120 +# Tests to ensure static compilation maintains guarantees for one-hot encodings +[[tests]] +name = "correctness static control, one-hot fsms" +paths = [ + "tests/correctness/static-control/*.futil", + "tests/correctness/group-static-promotion/*.futil", +] +cmd = """ +fud exec --from calyx --to jq \ + --through verilog \ + --through dat \ + -s calyx.exec './target/debug/calyx' \ + -s calyx.flags '-p all -d group2invoke -x compile-static:one-hot-cutoff=500' \ + -s verilog.cycle_limit 500 \ + -s verilog.data {}.data \ + {} -q +""" +timeout = 120 + [[tests]] name = "numeric types correctness and parsing" paths = [ diff --git a/tests/backend/verilog/big-const.expect b/tests/backend/verilog/big-const.expect index 022f6c0b44..a494c3376a 100644 --- a/tests/backend/verilog/big-const.expect +++ b/tests/backend/verilog/big-const.expect @@ -34,6 +34,16 @@ module std_add #( assign out = left + right; endmodule +module std_lsh #( + parameter WIDTH = 32 +) ( + input wire logic [WIDTH-1:0] left, + input wire logic [WIDTH-1:0] right, + output logic [WIDTH-1:0] out +); +assign out = left << right; +endmodule + module std_reg #( parameter WIDTH = 32 ) ( @@ -55,6 +65,27 @@ always_ff @(posedge clk) begin end endmodule +module init_one_reg #( + parameter WIDTH = 32 +) ( + input wire logic [WIDTH-1:0] in, + input wire logic write_en, + input wire logic clk, + input wire logic reset, + output logic [WIDTH-1:0] out, + output logic done +); +always_ff @(posedge clk) begin + if (reset) begin + out <= 1; + done <= 0; + end else if (write_en) begin + out <= in; + done <= 1'd1; + end else done <= 1'd0; + end +endmodule + module main( input logic go, input logic clk, diff --git a/tests/backend/verilog/data-instance.expect b/tests/backend/verilog/data-instance.expect index 95e7b21744..e260119fc4 100644 --- a/tests/backend/verilog/data-instance.expect +++ b/tests/backend/verilog/data-instance.expect @@ -34,6 +34,16 @@ module std_add #( assign out = left + right; endmodule +module std_lsh #( + parameter WIDTH = 32 +) ( + input wire logic [WIDTH-1:0] left, + input wire logic [WIDTH-1:0] right, + output logic [WIDTH-1:0] out +); +assign out = left << right; +endmodule + module std_reg #( parameter WIDTH = 32 ) ( @@ -55,6 +65,27 @@ always_ff @(posedge clk) begin end endmodule +module init_one_reg #( + parameter WIDTH = 32 +) ( + input wire logic [WIDTH-1:0] in, + input wire logic write_en, + input wire logic clk, + input wire logic reset, + output logic [WIDTH-1:0] out, + output logic done +); +always_ff @(posedge clk) begin + if (reset) begin + out <= 1; + done <= 0; + end else if (write_en) begin + out <= in; + done <= 1'd1; + end else done <= 1'd0; + end +endmodule + module main( input logic go, input logic g, diff --git a/tests/backend/verilog/memory-with-external-attribute.expect b/tests/backend/verilog/memory-with-external-attribute.expect index 5f150a861c..1d01427827 100644 --- a/tests/backend/verilog/memory-with-external-attribute.expect +++ b/tests/backend/verilog/memory-with-external-attribute.expect @@ -421,16 +421,6 @@ module std_le #( assign out = left <= right; endmodule -module std_lsh #( - parameter WIDTH = 32 -) ( - input wire logic [WIDTH-1:0] left, - input wire logic [WIDTH-1:0] right, - output logic [WIDTH-1:0] out -); - assign out = left << right; -endmodule - module std_rsh #( parameter WIDTH = 32 ) ( @@ -515,6 +505,16 @@ module std_add #( assign out = left + right; endmodule +module std_lsh #( + parameter WIDTH = 32 +) ( + input wire logic [WIDTH-1:0] left, + input wire logic [WIDTH-1:0] right, + output logic [WIDTH-1:0] out +); +assign out = left << right; +endmodule + module std_reg #( parameter WIDTH = 32 ) ( @@ -536,6 +536,27 @@ always_ff @(posedge clk) begin end endmodule +module init_one_reg #( + parameter WIDTH = 32 +) ( + input wire logic [WIDTH-1:0] in, + input wire logic write_en, + input wire logic clk, + input wire logic reset, + output logic [WIDTH-1:0] out, + output logic done +); +always_ff @(posedge clk) begin + if (reset) begin + out <= 1; + done <= 0; + end else if (write_en) begin + out <= in; + done <= 1'd1; + end else done <= 1'd0; + end +endmodule + module main( input logic go, input logic clk, diff --git a/tests/import/a.expect b/tests/import/a.expect index 93e1abff6d..c348a36e76 100644 --- a/tests/import/a.expect +++ b/tests/import/a.expect @@ -26,7 +26,6 @@ extern "/calyx/primitives/core.sv" { comb primitive std_neq<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: 1); comb primitive std_ge<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: 1); comb primitive std_le<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: 1); - comb primitive std_lsh<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: WIDTH); comb primitive std_rsh<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: WIDTH); comb primitive std_mux<"share"=1>[WIDTH](@data cond: 1, @data tru: WIDTH, @data fal: WIDTH) -> (out: WIDTH); } @@ -42,6 +41,9 @@ comb primitive std_wire<"share"=1>[WIDTH](@data in: WIDTH) -> (out: WIDTH) { comb primitive std_add<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: WIDTH) { assign out = left + right; } +comb primitive std_lsh<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: WIDTH) { + assign out = left << right; +} primitive std_reg<"state_share"=1>[WIDTH](@write_together @data in: WIDTH, @write_together @interval @go write_en: 1, @clk clk: 1, @reset reset: 1) -> (@stable out: WIDTH, @done done: 1) { always_ff @(posedge clk) begin if (reset) begin @@ -53,6 +55,17 @@ primitive std_reg<"state_share"=1>[WIDTH](@write_together @data in: WIDTH, @writ end else done <= 1'd0; end } +primitive init_one_reg<"state_share"=1>[WIDTH](@write_together @data in: WIDTH, @write_together @interval @go write_en: 1, @clk clk: 1, @reset reset: 1) -> (@stable out: WIDTH, @done done: 1) { + always_ff @(posedge clk) begin + if (reset) begin + out <= 1; + done <= 0; + end else if (write_en) begin + out <= in; + done <= 1'd1; + end else done <= 1'd0; + end +} component pow(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { cells {} wires {} diff --git a/tests/passes/compile-static-interface/compile-static-interface-one-cycle.expect b/tests/passes/compile-static-interface/compile-static-interface-one-cycle.expect index 54808b17a6..ea2f340f9f 100644 --- a/tests/passes/compile-static-interface/compile-static-interface-one-cycle.expect +++ b/tests/passes/compile-static-interface/compile-static-interface-one-cycle.expect @@ -29,7 +29,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { adder.left = fsm.out; adder.right = 1'd1; fsm.write_en = 1'd1; - fsm.in = fsm.out != 1'd0 ? adder.out; + fsm.in = !(fsm.out == 1'd0) ? adder.out; fsm.in = fsm.out == 1'd0 ? 1'd0; early_reset_static_invoke[done] = ud.out; } diff --git a/tests/passes/compile-static-interface/compile-static-interface-repeat.expect b/tests/passes/compile-static-interface/compile-static-interface-repeat.expect index 80b8d8e552..28c8dd37ff 100644 --- a/tests/passes/compile-static-interface/compile-static-interface-repeat.expect +++ b/tests/passes/compile-static-interface/compile-static-interface-repeat.expect @@ -19,7 +19,7 @@ static<6> component do_add(left: 32, right: 32, @go go: 1, @clk clk: 1, @reset r adder0.left = fsm0.out; adder0.right = 1'd1; fsm0.write_en = 1'd1; - fsm0.in = fsm0.out != 1'd0 ? adder0.out; + fsm0.in = !(fsm0.out == 1'd0) ? adder0.out; fsm0.in = fsm0.out == 1'd0 ? 1'd0; early_reset_a[done] = ud.out; } @@ -31,9 +31,10 @@ static<6> component do_add(left: 32, right: 32, @go go: 1, @clk clk: 1, @reset r adder.left = fsm.out; adder.right = 3'd1; fsm.write_en = 1'd1; - fsm.in = go & fsm.out == 3'd0 ? 3'd1; - fsm.in = fsm.out != 3'd0 & fsm.out != 3'd5 ? adder.out; + fsm.in = go & fsm.out == 3'd0 ? adder.out; + fsm.in = !(fsm.out == 3'd0) & !(fsm.out == 3'd5) ? adder.out; fsm.in = fsm.out == 3'd5 ? 3'd0; + fsm.in = !go & fsm.out == 3'd0 ? 3'd0; } control {} } @@ -53,7 +54,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { adder.left = fsm.out; adder.right = 3'd1; fsm.write_en = 1'd1; - fsm.in = fsm.out != 3'd5 ? adder.out; + fsm.in = !(fsm.out == 3'd5) ? adder.out; fsm.in = fsm.out == 3'd5 ? 3'd0; early_reset_static_invoke[done] = ud.out; } diff --git a/tests/passes/compile-static-interface/compile-static-interface.expect b/tests/passes/compile-static-interface/compile-static-interface.expect index 03aa4c5e00..66098d210c 100644 --- a/tests/passes/compile-static-interface/compile-static-interface.expect +++ b/tests/passes/compile-static-interface/compile-static-interface.expect @@ -19,9 +19,10 @@ static<2> component do_add(left: 32, right: 32, @go go: 1, @clk clk: 1, @reset r adder.left = fsm.out; adder.right = 2'd1; fsm.write_en = 1'd1; - fsm.in = go & fsm.out == 2'd0 ? 2'd1; - fsm.in = fsm.out != 2'd0 & fsm.out != 2'd1 ? adder.out; + fsm.in = go & fsm.out == 2'd0 ? adder.out; + fsm.in = !(fsm.out == 2'd0) & !(fsm.out == 2'd1) ? adder.out; fsm.in = fsm.out == 2'd1 ? 2'd0; + fsm.in = !go & fsm.out == 2'd0 ? 2'd0; } control {} } @@ -41,7 +42,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { adder.left = fsm.out; adder.right = 2'd1; fsm.write_en = 1'd1; - fsm.in = fsm.out != 2'd1 ? adder.out; + fsm.in = !(fsm.out == 2'd1) ? adder.out; fsm.in = fsm.out == 2'd1 ? 2'd0; early_reset_static_invoke[done] = ud.out; } diff --git a/tests/passes/compile-static-interface/interface-one-hot.expect b/tests/passes/compile-static-interface/interface-one-hot.expect new file mode 100644 index 0000000000..df247b1f50 --- /dev/null +++ b/tests/passes/compile-static-interface/interface-one-hot.expect @@ -0,0 +1,102 @@ +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; +static<6> component do_add<"promoted"=1>(left: 32, right: 32, @go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { + cells { + add = std_add(32); + r = std_reg(32); + @generated fsm = init_one_reg(6); + @generated bw_0_1 = std_wire(1); + @generated slicer = std_bit_slice(6, 0, 0, 1); + @generated bw_1_6 = std_wire(1); + @generated slicer0 = std_bit_slice(6, 1, 5, 5); + @generated lsh = std_lsh(6); + @generated bw_5_6 = std_wire(1); + @generated slicer1 = std_bit_slice(6, 5, 5, 1); + @generated sig_reg = std_reg(1); + @generated fsm0 = init_one_reg(1); + @generated lsh0 = std_lsh(1); + @generated bw_0_10 = std_wire(1); + @generated slicer2 = std_bit_slice(1, 0, 0, 1); + @generated ud = undef(1); + } + wires { + group early_reset_a { + r.write_en = 1'd1; + add.right = right; + add.left = r.out; + r.in = add.out; + lsh0.left = fsm0.out; + lsh0.right = 1'd1; + fsm0.write_en = 1'd1; + fsm0.in = !bw_0_10.out ? lsh0.out; + fsm0.in = bw_0_10.out ? 1'd1; + early_reset_a[done] = ud.out; + } + slicer.in = fsm.out; + bw_0_1.in = slicer.out != 1'd0 ? 1'd1; + slicer0.in = fsm.out; + bw_1_6.in = slicer0.out != 5'd0 ? 1'd1; + slicer1.in = fsm.out; + bw_5_6.in = slicer1.out != 1'd0 ? 1'd1; + r.write_en = go & bw_0_1.out ? 1'd1; + add.right = go & bw_0_1.out ? right; + add.left = go & bw_0_1.out ? left; + r.in = go & bw_0_1.out ? add.out; + early_reset_a[go] = bw_1_6.out ? 1'd1; + lsh.left = fsm.out; + lsh.right = 6'd1; + fsm.write_en = 1'd1; + fsm.in = go & bw_0_1.out ? lsh.out; + fsm.in = !bw_0_1.out & !bw_5_6.out ? lsh.out; + fsm.in = bw_5_6.out ? 6'd1; + fsm.in = !go & bw_0_1.out ? 6'd1; + sig_reg.write_en = bw_0_1.out ? 1'd1; + sig_reg.in = go ? 1'd1; + sig_reg.in = !go ? 1'd0; + done = bw_0_1.out & sig_reg.out ? 1'd1; + slicer2.in = fsm0.out; + bw_0_10.in = slicer2.out != 1'd0 ? 1'd1; + } + control {} +} +component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { + cells { + a = do_add(); + @generated fsm = init_one_reg(6); + @generated bw_0_1 = std_wire(1); + @generated slicer = std_bit_slice(6, 0, 0, 1); + @generated lsh = std_lsh(6); + @generated bw_5_6 = std_wire(1); + @generated slicer0 = std_bit_slice(6, 5, 5, 1); + @generated ud = undef(1); + @generated signal_reg = std_reg(1); + } + wires { + group early_reset_static_invoke { + a.go = bw_0_1.out ? 1'd1; + a.left = 32'd5; + a.right = 32'd6; + lsh.left = fsm.out; + lsh.right = 6'd1; + fsm.write_en = 1'd1; + fsm.in = !bw_5_6.out ? lsh.out; + fsm.in = bw_5_6.out ? 6'd1; + early_reset_static_invoke[done] = ud.out; + } + group wrapper_early_reset_static_invoke { + early_reset_static_invoke[go] = 1'd1; + signal_reg.write_en = bw_0_1.out & !signal_reg.out ? 1'd1; + signal_reg.in = bw_0_1.out & !signal_reg.out ? 1'd1; + wrapper_early_reset_static_invoke[done] = bw_0_1.out & signal_reg.out ? 1'd1; + } + slicer.in = fsm.out; + bw_0_1.in = slicer.out != 1'd0 ? 1'd1; + slicer0.in = fsm.out; + bw_5_6.in = slicer0.out != 1'd0 ? 1'd1; + signal_reg.write_en = bw_0_1.out & signal_reg.out ? 1'd1; + signal_reg.in = bw_0_1.out & signal_reg.out ? 1'd0; + } + control { + wrapper_early_reset_static_invoke; + } +} diff --git a/tests/passes/compile-static-interface/interface-one-hot.futil b/tests/passes/compile-static-interface/interface-one-hot.futil new file mode 100644 index 0000000000..3703cb4289 --- /dev/null +++ b/tests/passes/compile-static-interface/interface-one-hot.futil @@ -0,0 +1,45 @@ +// -p validate -p compile-invoke -p static-inline -p dead-group-removal -p add-guard -p simplify-static-guards -p compile-static -x compile-static:one-hot-cutoff=11 +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; + +static<6> component do_add<"promoted"=1>(left: 32, right: 32) -> () { + cells { + add = std_add(32); + r = std_reg(32); + } + wires { + static<1> group a { + add.left = r.out; + add.right = right; + r.in = add.out; + r.write_en = 1'd1; + } + + static<1> group b { + add.left = left; + add.right = right; + r.in = add.out; + r.write_en = 1'd1; + } + } + control { + static seq { + b; + static repeat 5 { + a; + } + } + } + +} + +component main () -> () { + cells { + a = do_add(); + } + wires {} + + control { + static invoke a(left=32'd5, right = 32'd6)(); + } +} \ No newline at end of file diff --git a/tests/passes/compile-static/interface-and-cs.expect b/tests/passes/compile-static/interface-and-cs.expect index 80b8d8e552..28c8dd37ff 100644 --- a/tests/passes/compile-static/interface-and-cs.expect +++ b/tests/passes/compile-static/interface-and-cs.expect @@ -19,7 +19,7 @@ static<6> component do_add(left: 32, right: 32, @go go: 1, @clk clk: 1, @reset r adder0.left = fsm0.out; adder0.right = 1'd1; fsm0.write_en = 1'd1; - fsm0.in = fsm0.out != 1'd0 ? adder0.out; + fsm0.in = !(fsm0.out == 1'd0) ? adder0.out; fsm0.in = fsm0.out == 1'd0 ? 1'd0; early_reset_a[done] = ud.out; } @@ -31,9 +31,10 @@ static<6> component do_add(left: 32, right: 32, @go go: 1, @clk clk: 1, @reset r adder.left = fsm.out; adder.right = 3'd1; fsm.write_en = 1'd1; - fsm.in = go & fsm.out == 3'd0 ? 3'd1; - fsm.in = fsm.out != 3'd0 & fsm.out != 3'd5 ? adder.out; + fsm.in = go & fsm.out == 3'd0 ? adder.out; + fsm.in = !(fsm.out == 3'd0) & !(fsm.out == 3'd5) ? adder.out; fsm.in = fsm.out == 3'd5 ? 3'd0; + fsm.in = !go & fsm.out == 3'd0 ? 3'd0; } control {} } @@ -53,7 +54,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { adder.left = fsm.out; adder.right = 3'd1; fsm.write_en = 1'd1; - fsm.in = fsm.out != 3'd5 ? adder.out; + fsm.in = !(fsm.out == 3'd5) ? adder.out; fsm.in = fsm.out == 3'd5 ? 3'd0; early_reset_static_invoke[done] = ud.out; } diff --git a/tests/passes/compile-static/one-hot.expect b/tests/passes/compile-static/one-hot.expect new file mode 100644 index 0000000000..6756bfdc95 --- /dev/null +++ b/tests/passes/compile-static/one-hot.expect @@ -0,0 +1,76 @@ +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; +import "primitives/pipelined.futil"; +component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { + cells { + a = std_reg(2); + b = std_reg(2); + c = std_reg(2); + d = std_reg(2); + @generated fsm = init_one_reg(10); + @generated bw_0_2 = std_wire(1); + @generated slicer = std_bit_slice(10, 0, 1, 2); + @generated bw_1_2 = std_wire(1); + @generated slicer0 = std_bit_slice(10, 1, 1, 1); + @generated bw_7_10 = std_wire(1); + @generated slicer1 = std_bit_slice(10, 7, 9, 3); + @generated bw_6_8 = std_wire(1); + @generated slicer2 = std_bit_slice(10, 6, 7, 2); + @generated bw_3_7 = std_wire(1); + @generated slicer3 = std_bit_slice(10, 3, 6, 4); + @generated bw_0_1 = std_wire(1); + @generated slicer4 = std_bit_slice(10, 0, 0, 1); + @generated lsh = std_lsh(10); + @generated bw_9_10 = std_wire(1); + @generated slicer5 = std_bit_slice(10, 9, 9, 1); + @generated ud = undef(1); + @generated signal_reg = std_reg(1); + } + wires { + group early_reset_static_seq { + a.in = bw_0_2.out ? 2'd0; + a.write_en = bw_0_2.out ? 1'd1; + b.in = bw_1_2.out ? 2'd1; + b.write_en = bw_1_2.out ? 1'd1; + b.write_en = bw_7_10.out ? 1'd1; + b.in = bw_7_10.out ? 2'd1; + c.write_en = bw_6_8.out ? 1'd1; + c.in = bw_6_8.out ? 2'd1; + d.write_en = bw_3_7.out ? 1'd1; + d.in = bw_3_7.out ? 2'd1; + d.write_en = bw_0_1.out ? 1'd1; + d.in = bw_0_1.out ? 2'd1; + lsh.left = fsm.out; + lsh.right = 10'd1; + fsm.write_en = 1'd1; + fsm.in = !bw_9_10.out ? lsh.out; + fsm.in = bw_9_10.out ? 10'd1; + early_reset_static_seq[done] = ud.out; + } + group wrapper_early_reset_static_seq { + early_reset_static_seq[go] = 1'd1; + signal_reg.write_en = bw_0_1.out & !signal_reg.out ? 1'd1; + signal_reg.in = bw_0_1.out & !signal_reg.out ? 1'd1; + wrapper_early_reset_static_seq[done] = bw_0_1.out & signal_reg.out ? 1'd1; + } + slicer.in = fsm.out; + bw_0_2.in = slicer.out != 2'd0 ? 1'd1; + slicer0.in = fsm.out; + bw_1_2.in = slicer0.out != 1'd0 ? 1'd1; + slicer1.in = fsm.out; + bw_7_10.in = slicer1.out != 3'd0 ? 1'd1; + slicer2.in = fsm.out; + bw_6_8.in = slicer2.out != 2'd0 ? 1'd1; + slicer3.in = fsm.out; + bw_3_7.in = slicer3.out != 4'd0 ? 1'd1; + slicer4.in = fsm.out; + bw_0_1.in = slicer4.out != 1'd0 ? 1'd1; + slicer5.in = fsm.out; + bw_9_10.in = slicer5.out != 1'd0 ? 1'd1; + signal_reg.write_en = bw_0_1.out & signal_reg.out ? 1'd1; + signal_reg.in = bw_0_1.out & signal_reg.out ? 1'd0; + } + control { + wrapper_early_reset_static_seq; + } +} diff --git a/tests/passes/compile-static/one-hot.futil b/tests/passes/compile-static/one-hot.futil new file mode 100644 index 0000000000..ff00eb1614 --- /dev/null +++ b/tests/passes/compile-static/one-hot.futil @@ -0,0 +1,32 @@ +// -p well-formed -p compile-static -x compile-static:one-hot-cutoff=11 -p dead-group-removal -p remove-ids + +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; +import "primitives/pipelined.futil"; +component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { + cells { + a = std_reg(2); + b = std_reg(2); + c = std_reg(2); + d = std_reg(2); + } + wires { + static<10> group static_seq { + a.in = %[0:2] ? 2'd0; + a.write_en = %[0:2] ? 1'd1; + b.in = %1 ? 2'd1; + b.write_en = %1 ? 1'd1; + b.write_en = %[7:10] ? 1'd1; + b.in = %[7:10] ? 2'd1; + c.write_en = %[6:8] ? 1'd1; + c.in = %[6:8] ? 2'd1; + d.write_en = %[3:7] ? 1'd1; + d.in = %[3:7] ? 2'd1; + d.write_en = %0 ? 1'd1; + d.in = %0 ? 2'd1; + } + } + control { + static_seq; + } +} diff --git a/tests/passes/compile-static/rewrite-group-go.expect b/tests/passes/compile-static/rewrite-group-go.expect index d3ec1c6fd7..fa281b5fd4 100644 --- a/tests/passes/compile-static/rewrite-group-go.expect +++ b/tests/passes/compile-static/rewrite-group-go.expect @@ -33,7 +33,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { adder.left = fsm.out; adder.right = 2'd1; fsm.write_en = 1'd1; - fsm.in = fsm.out != 2'd1 ? adder.out; + fsm.in = !(fsm.out == 2'd1) ? adder.out; fsm.in = fsm.out == 2'd1 ? 2'd0; early_reset_A[done] = ud.out; } @@ -42,7 +42,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { adder0.left = fsm0.out; adder0.right = 3'd1; fsm0.write_en = 1'd1; - fsm0.in = fsm0.out != 3'd5 ? adder0.out; + fsm0.in = !(fsm0.out == 3'd5) ? adder0.out; fsm0.in = fsm0.out == 3'd5 ? 3'd0; early_reset_run_A_thrice[done] = ud0.out; } diff --git a/tests/passes/compile-static/rewrite-static-while-nested.expect b/tests/passes/compile-static/rewrite-static-while-nested.expect index 466872d406..f54d011737 100644 --- a/tests/passes/compile-static/rewrite-static-while-nested.expect +++ b/tests/passes/compile-static/rewrite-static-while-nested.expect @@ -41,7 +41,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { adder0.left = fsm.out; adder0.right = 2'd1; fsm.write_en = 1'd1; - fsm.in = fsm.out != 2'd0 ? adder0.out; + fsm.in = !(fsm.out == 2'd0) ? adder0.out; fsm.in = fsm.out == 2'd0 ? 2'd0; early_reset_A2[done] = ud0.out; } @@ -59,7 +59,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { adder1.left = fsm.out; adder1.right = 2'd1; fsm.write_en = 1'd1; - fsm.in = fsm.out != 2'd1 ? adder1.out; + fsm.in = !(fsm.out == 2'd1) ? adder1.out; fsm.in = fsm.out == 2'd1 ? 2'd0; early_reset_static_seq[done] = ud1.out; } diff --git a/tests/passes/compile-static/rewrite-static-while.expect b/tests/passes/compile-static/rewrite-static-while.expect index 10a3529b4b..b0d5e77611 100644 --- a/tests/passes/compile-static/rewrite-static-while.expect +++ b/tests/passes/compile-static/rewrite-static-while.expect @@ -25,7 +25,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { adder0.left = fsm.out; adder0.right = 2'd1; fsm.write_en = 1'd1; - fsm.in = fsm.out != 2'd0 ? adder0.out; + fsm.in = !(fsm.out == 2'd0) ? adder0.out; fsm.in = fsm.out == 2'd0 ? 2'd0; early_reset_B[done] = ud0.out; } @@ -41,7 +41,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { adder1.left = fsm.out; adder1.right = 2'd1; fsm.write_en = 1'd1; - fsm.in = fsm.out != 2'd1 ? adder1.out; + fsm.in = !(fsm.out == 2'd1) ? adder1.out; fsm.in = fsm.out == 2'd1 ? 2'd0; early_reset_static_seq[done] = ud1.out; } diff --git a/tests/passes/compile-static/separate-fsms.expect b/tests/passes/compile-static/separate-fsms.expect index 9082db0059..df96899593 100644 --- a/tests/passes/compile-static/separate-fsms.expect +++ b/tests/passes/compile-static/separate-fsms.expect @@ -33,7 +33,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { adder.left = fsm.out; adder.right = 1'd1; fsm.write_en = 1'd1; - fsm.in = fsm.out != 1'd0 ? adder.out; + fsm.in = !(fsm.out == 1'd0) ? adder.out; fsm.in = fsm.out == 1'd0 ? 1'd0; early_reset_A[done] = ud.out; } @@ -43,7 +43,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { adder0.left = fsm0.out; adder0.right = 1'd1; fsm0.write_en = 1'd1; - fsm0.in = fsm0.out != 1'd0 ? adder0.out; + fsm0.in = !(fsm0.out == 1'd0) ? adder0.out; fsm0.in = fsm0.out == 1'd0 ? 1'd0; early_reset_C[done] = ud0.out; } @@ -53,7 +53,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { adder1.left = fsm1.out; adder1.right = 1'd1; fsm1.write_en = 1'd1; - fsm1.in = fsm1.out != 1'd0 ? adder1.out; + fsm1.in = !(fsm1.out == 1'd0) ? adder1.out; fsm1.in = fsm1.out == 1'd0 ? 1'd0; early_reset_D[done] = ud1.out; } @@ -63,7 +63,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { adder2.left = fsm2.out; adder2.right = 1'd1; fsm2.write_en = 1'd1; - fsm2.in = fsm2.out != 1'd0 ? adder2.out; + fsm2.in = !(fsm2.out == 1'd0) ? adder2.out; fsm2.in = fsm2.out == 1'd0 ? 1'd0; early_reset_B[done] = ud2.out; } @@ -73,7 +73,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { adder3.left = fsm3.out; adder3.right = 3'd1; fsm3.write_en = 1'd1; - fsm3.in = fsm3.out != 3'd5 ? adder3.out; + fsm3.in = !(fsm3.out == 3'd5) ? adder3.out; fsm3.in = fsm3.out == 3'd5 ? 3'd0; early_reset_run_A_and_D[done] = ud3.out; }