diff --git a/calyx-opt/src/analysis/mod.rs b/calyx-opt/src/analysis/mod.rs index a187615f82..7406537655 100644 --- a/calyx-opt/src/analysis/mod.rs +++ b/calyx-opt/src/analysis/mod.rs @@ -21,6 +21,7 @@ mod read_write_set; mod schedule_conflicts; mod share_set; mod static_par_timing; +mod static_schedule; mod variable_detection; pub use compaction_analysis::CompactionAnalysis; @@ -42,4 +43,5 @@ pub use read_write_set::{AssignmentAnalysis, ReadWriteSet}; pub use schedule_conflicts::ScheduleConflicts; pub use share_set::ShareSet; pub use static_par_timing::StaticParTiming; +pub use static_schedule::{StaticFSM, StaticSchedule}; pub use variable_detection::VariableDetection; diff --git a/calyx-opt/src/analysis/static_schedule.rs b/calyx-opt/src/analysis/static_schedule.rs new file mode 100644 index 0000000000..fca632031b --- /dev/null +++ b/calyx-opt/src/analysis/static_schedule.rs @@ -0,0 +1,428 @@ +use crate::passes::math_utilities::get_bit_width_from; +use calyx_ir::{self as ir}; +use calyx_ir::{build_assignments, Nothing}; +use calyx_ir::{guard, structure}; +use ir::Guard; +use itertools::Itertools; +use std::collections::{HashSet, VecDeque}; +use std::rc::Rc; + +#[derive(Debug, Clone, Copy)] +// Define an enum called Fruit +enum FSMEncoding { + Binary, + _OneHot, +} + +#[derive(Debug)] +pub struct StaticFSM { + _num_states: u64, + _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, +} +impl StaticFSM { + // Builds a static_fsm from: num_states and encoding type. + fn from_basic_info( + num_states: u64, + encoding: FSMEncoding, + 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]); + StaticFSM { + _num_states: num_states, + _encoding: encoding, + bitwidth: fsm_size, + cell: fsm, + } + } + + // Returns assignments that make the current fsm count to n + // and then reset back to 0. + // `incr_condition`` is an optional guard: if it is none, then the fsm will + // unconditionally increment. + // If it actually holds a `guard`, then we will only start counting once + // the condition holds. + // (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.) + pub fn count_to_n( + &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() + } + } + } + + // Returns a guard that takes a (beg, end) `query`, and returns the equivalent + // guard to `beg <= fsm.out < end`. + pub fn query_between( + &self, + builder: &mut ir::Builder, + query: (u64, u64), + ) -> Box> { + // Only support Binary encoding currently. + assert!(matches!(self._encoding, FSMEncoding::Binary)); + let (beg, end) = query; + let fsm_cell = Rc::clone(&self.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); + let g = guard!(fsm_cell["out"] == interval_const["out"]); + Box::new(g) + } else if beg == 0 { + // if beg == 0, then we only need to check if fsm < end + let end_const = builder.add_constant(end, self.bitwidth); + let lt: ir::Guard = + guard!(fsm_cell["out"] < end_const["out"]); + Box::new(lt) + } else { + // otherwise, check if fsm >= beg & fsm < end + let beg_const = builder.add_constant(beg, self.bitwidth); + let end_const = builder.add_constant(end, self.bitwidth); + let beg_guard: ir::Guard = + guard!(fsm_cell["out"] >= beg_const["out"]); + let end_guard: ir::Guard = + guard!(fsm_cell["out"] < end_const["out"]); + Box::new(ir::Guard::And(Box::new(beg_guard), Box::new(end_guard))) + } + } + + // 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() + } + + // Return the bitwidth of an FSM object + pub fn get_bitwidth(&self) -> u64 { + self.bitwidth + } +} + +/// Represents a static schedule. +#[derive(Debug, Default)] +pub struct StaticSchedule { + /// Number of states for the FSM + /// (this is just the latency of the static island-- or that of the largest + /// static island, if there are multiple islands) + num_states: u64, + /// The queries that the FSM needs to support. + /// E.g., `lhs = %[2:3] ? rhs` corresponds to (2,3). + queries: HashSet<(u64, u64)>, + /// The static groups the FSM will schedule. It is a vec because sometimes + /// the same FSM will handle two different static islands. + pub static_groups: Vec>, +} + +impl From>> for StaticSchedule { + /// Builds a StaticSchedule object from a vec of static groups. + fn from(static_groups: Vec>) -> Self { + let mut schedule = StaticSchedule { + static_groups, + ..Default::default() + }; + schedule.num_states = 0; + for static_group in &schedule.static_groups { + // Getting self.queries + for static_assign in &static_group.borrow().assignments { + for query in Self::queries_from_guard(&static_assign.guard) { + schedule.queries.insert(query); + } + } + // Getting self.num_states + schedule.num_states = std::cmp::max( + schedule.num_states, + static_group.borrow().get_latency(), + ); + } + schedule + } +} + +impl StaticSchedule { + /// Given a guard, returns the queries that the static FSM (i.e., counter) + /// will make. + fn queries_from_guard( + guard: &ir::Guard, + ) -> Vec<(u64, u64)> { + match guard { + ir::Guard::Or(l, r) | ir::Guard::And(l, r) => { + let mut lvec = Self::queries_from_guard(l); + let rvec = Self::queries_from_guard(r); + lvec.extend(rvec); + lvec + } + ir::Guard::Not(g) => Self::queries_from_guard(g), + ir::Guard::Port(_) + | ir::Guard::CompOp(_, _, _) + | ir::Guard::True => vec![], + ir::Guard::Info(static_timing) => { + vec![static_timing.get_interval()] + } + } + } + + /// Realizes a StaticSchedule (i.e., instantiates the FSMs) + /// If `self.static_groups = vec![group1, group2, group3, ...]`` + /// Then `realize_schedule()` returns vecdeque![a1, a2, a3] + /// Where a1 are the assignments for group1, a2 are the assignments + /// to group2, etc. + /// It also returns the StaticFSM object. + /// + /// We also have a bool argument `static_component_interface`. + /// If you are the entire control of a static component, it is slightly different, + /// because we need to separate the first cycle (%[0:n] -> %0 | [%1:n]) and + /// replace %0 with `comp.go & %0`. (We do `comp.go & %0` rather than `%0` bc + /// we want the clients to be able to assert `go` for n cycles and the + /// component still works as expected). + pub fn realize_schedule( + &mut self, + builder: &mut ir::Builder, + static_component_interface: bool, + ) -> (VecDeque>>, StaticFSM) { + // First build the fsm we will use to realize the schedule. + let fsm_object = StaticFSM::from_basic_info( + self.num_states, + FSMEncoding::Binary, + builder, + ); + + // Instantiate the vecdeque. + let mut res = VecDeque::new(); + for static_group in &mut self.static_groups { + let mut static_group_ref = static_group.borrow_mut(); + // Separate the first cycle (if necessary) and then realize the + // static timing guards (e.g., %[2:3] -> 2 <= fsm < 3). + let group_assigns = + static_group_ref.assignments.drain(..).collect_vec(); + let static_assigns = if static_component_interface { + group_assigns + .into_iter() + .map(|assign| { + if static_component_interface { + Self::handle_static_interface( + assign, + Rc::clone(&builder.component.signature), + ) + } else { + assign + } + }) + .collect_vec() + } else { + group_assigns + }; + let mut assigns: Vec> = static_assigns + .into_iter() + .map(|static_assign| { + Self::make_assign_dyn(static_assign, &fsm_object, builder) + }) + .collect(); + // For static components, we don't unconditionally start counting. + // We must only start counting when `comp.go` is high. + let fsm_incr_condition = if static_component_interface { + let comp_sig = Rc::clone(&builder.component.signature); + let g = guard!(comp_sig["go"]); + Some(g) + } else { + None + }; + // We need to add assignments that makes the FSM count to n. + assigns.extend(fsm_object.count_to_n( + builder, + static_group_ref.get_latency() - 1, + fsm_incr_condition, + )); + + res.push_back(assigns); + } + (res, fsm_object) + } + + // Takes in a static guard `guard`, and returns equivalent dynamic guard + // The only thing that actually changes is the Guard::Info case + // We need to turn static_timing to dynamic guards using `fsm`. + // E.g.: %[2:3] gets turned into fsm.out >= 2 & fsm.out < 3 + // is_static_comp is necessary becasue it ... + fn make_guard_dyn( + guard: ir::Guard, + fsm_object: &StaticFSM, + builder: &mut ir::Builder, + ) -> Box> { + match guard { + ir::Guard::Or(l, r) => Box::new(ir::Guard::Or( + Self::make_guard_dyn(*l, fsm_object, builder), + Self::make_guard_dyn(*r, fsm_object, builder), + )), + ir::Guard::And(l, r) => Box::new(ir::Guard::And( + Self::make_guard_dyn(*l, fsm_object, builder), + Self::make_guard_dyn(*r, fsm_object, builder), + )), + ir::Guard::Not(g) => Box::new(ir::Guard::Not( + Self::make_guard_dyn(*g, fsm_object, builder), + )), + ir::Guard::CompOp(op, l, r) => { + Box::new(ir::Guard::CompOp(op, l, r)) + } + ir::Guard::Port(p) => Box::new(ir::Guard::Port(p)), + ir::Guard::True => Box::new(ir::Guard::True), + ir::Guard::Info(static_timing) => { + fsm_object.query_between(builder, static_timing.get_interval()) + } + } + } + + // Takes in static assignment `assign` and returns a dynamic assignments + // Mainly transforms the guards from %[2:3] -> fsm.out >= 2 & fsm.out <= 3 + fn make_assign_dyn( + assign: ir::Assignment, + fsm_object: &StaticFSM, + builder: &mut ir::Builder, + ) -> ir::Assignment { + ir::Assignment { + src: assign.src, + dst: assign.dst, + attributes: assign.attributes, + guard: Self::make_guard_dyn(*assign.guard, fsm_object, builder), + } + } + + // Looks recursively thru guard to transform %[0:n] into %0 | %[1:n]. + fn handle_static_interface_guard( + guard: ir::Guard, + comp_sig: ir::RRC, + ) -> ir::Guard { + match guard { + ir::Guard::Info(st) => { + let (beg, end) = st.get_interval(); + if beg == 0 { + // Replace %[0:n] -> (%0 & comp.go) | %[1:n] + // Cannot just do comp.go | %[1:n] because we want + // clients to be able to assert `comp.go` even after the first + // cycle w/o affecting correctness. + let first_cycle = + ir::Guard::Info(ir::StaticTiming::new((0, 1))); + let comp_go = guard!(comp_sig["go"]); + let first_and_go = ir::Guard::and(comp_go, first_cycle); + if end == 1 { + return first_and_go; + } else { + let after = + ir::Guard::Info(ir::StaticTiming::new((1, end))); + let cong = ir::Guard::or(first_and_go, after); + return cong; + } + } + guard + } + ir::Guard::And(l, r) => { + let left = Self::handle_static_interface_guard( + *l, + Rc::clone(&comp_sig), + ); + let right = Self::handle_static_interface_guard(*r, comp_sig); + ir::Guard::and(left, right) + } + ir::Guard::Or(l, r) => { + let left = Self::handle_static_interface_guard( + *l, + Rc::clone(&comp_sig), + ); + let right = Self::handle_static_interface_guard(*r, comp_sig); + ir::Guard::or(left, right) + } + ir::Guard::Not(g) => { + let a = Self::handle_static_interface_guard(*g, comp_sig); + ir::Guard::Not(Box::new(a)) + } + _ => guard, + } + } + + // Looks recursively thru assignment's guard to %[0:n] into %0 | %[1:n]. + fn handle_static_interface( + assign: ir::Assignment, + comp_sig: ir::RRC, + ) -> ir::Assignment { + ir::Assignment { + src: assign.src, + dst: assign.dst, + attributes: assign.attributes, + guard: Box::new(Self::handle_static_interface_guard( + *assign.guard, + comp_sig, + )), + } + } +} diff --git a/calyx-opt/src/default_passes.rs b/calyx-opt/src/default_passes.rs index 719d34b733..a08d784c1c 100644 --- a/calyx-opt/src/default_passes.rs +++ b/calyx-opt/src/default_passes.rs @@ -1,8 +1,8 @@ //! Defines the default passes available to [PassManager]. use crate::passes::{ AddGuard, Canonicalize, CellShare, ClkInsertion, CollapseControl, CombProp, - CompileInvoke, CompileRepeat, CompileStatic, CompileStaticInterface, - CompileSync, CompileSyncWithoutSyncReg, ComponentInliner, DataPathInfer, + CompileInvoke, CompileRepeat, CompileStatic, CompileSync, + CompileSyncWithoutSyncReg, ComponentInliner, DataPathInfer, DeadAssignmentRemoval, DeadCellRemoval, DeadGroupRemoval, DefaultAssigns, DiscoverExternal, ExternalToRef, Externalize, GoInsertion, GroupToInvoke, GroupToSeq, HoleInliner, InferShare, LowerGuards, MergeAssign, Papercut, @@ -50,7 +50,6 @@ impl PassManager { pm.register_pass::()?; pm.register_pass::()?; pm.register_pass::()?; - pm.register_pass::()?; // Lowering passes pm.register_pass::()?; @@ -110,9 +109,8 @@ impl PassManager { DeadGroupRemoval, // Static inliner generates lots of dead groups SimplifyStaticGuards, AddGuard, - CompileStaticInterface, - DeadGroupRemoval, CompileStatic, + DeadGroupRemoval, TopDownCompileControl ] ); diff --git a/calyx-opt/src/passes/compile_static.rs b/calyx-opt/src/passes/compile_static.rs index beaf6acca2..87d676a0a3 100644 --- a/calyx-opt/src/passes/compile_static.rs +++ b/calyx-opt/src/passes/compile_static.rs @@ -1,10 +1,9 @@ -use super::math_utilities::get_bit_width_from; -use crate::analysis::GraphColoring; +use crate::analysis::{GraphColoring, StaticFSM, StaticSchedule}; use crate::traversal::{Action, Named, VisResult, Visitor}; use calyx_ir as ir; use calyx_ir::{guard, structure, GetAttributes}; use calyx_utils::Error; -use ir::{build_assignments, Nothing, StaticTiming, RRC}; +use ir::{build_assignments, RRC}; use itertools::Itertools; use std::collections::{HashMap, HashSet}; use std::ops::Not; @@ -19,10 +18,8 @@ pub struct CompileStatic { wrapper_map: HashMap, /// maps fsm names to their corresponding signal_reg signal_reg_map: HashMap, - /// maps reset_early_group names to (fsm name, fsm_width) - fsm_info_map: HashMap, - /// rewrites `static_group[go]` to `dynamic_group[go]` - group_rewrite: ir::rewriter::PortRewriteMap, + /// maps reset_early_group names to StaticFSM object + fsm_info_map: HashMap>, } impl Named for CompileStatic { @@ -35,129 +32,6 @@ impl Named for CompileStatic { } } -// Takes in a static guard `guard`, and returns equivalent dynamic guard -// The only thing that actually changes is the Guard::Info case -// We need to turn static_timing to dynamic guards using `fsm`. -// E.g.: %[2:3] gets turned into fsm.out >= 2 & fsm.out < 3 -pub(super) fn make_guard_dyn( - guard: ir::Guard, - fsm: &ir::RRC, - fsm_size: u64, - builder: &mut ir::Builder, - is_static_comp: bool, - comp_sig: Option>, -) -> Box> { - match guard { - ir::Guard::Or(l, r) => Box::new(ir::Guard::Or( - make_guard_dyn( - *l, - fsm, - fsm_size, - builder, - is_static_comp, - comp_sig.clone(), - ), - make_guard_dyn( - *r, - fsm, - fsm_size, - builder, - is_static_comp, - comp_sig, - ), - )), - ir::Guard::And(l, r) => Box::new(ir::Guard::And( - make_guard_dyn( - *l, - fsm, - fsm_size, - builder, - is_static_comp, - comp_sig.clone(), - ), - make_guard_dyn( - *r, - fsm, - fsm_size, - builder, - is_static_comp, - comp_sig, - ), - )), - ir::Guard::Not(g) => Box::new(ir::Guard::Not(make_guard_dyn( - *g, - fsm, - fsm_size, - builder, - is_static_comp, - comp_sig, - ))), - ir::Guard::CompOp(op, l, r) => Box::new(ir::Guard::CompOp(op, l, r)), - ir::Guard::Port(p) => Box::new(ir::Guard::Port(p)), - ir::Guard::True => Box::new(ir::Guard::True), - ir::Guard::Info(static_timing) => { - let (beg, end) = static_timing.get_interval(); - if is_static_comp && beg == 0 && end == 1 { - let interval_const = builder.add_constant(0, fsm_size); - let sig = comp_sig.unwrap(); - let g1 = guard!(sig["go"]); - let g2 = guard!(fsm["out"] == interval_const["out"]); - let g = ir::Guard::And(Box::new(g1), Box::new(g2)); - return Box::new(g); - } - if beg + 1 == end { - // if beg + 1 == end then we only need to check if fsm == beg - let interval_const = builder.add_constant(beg, fsm_size); - let g = guard!(fsm["out"] == interval_const["out"]); - Box::new(g) - } else if beg == 0 { - // if beg == 0, then we only need to check if fsm < end - let end_const = builder.add_constant(end, fsm_size); - let lt: ir::Guard = - guard!(fsm["out"] < end_const["out"]); - Box::new(lt) - } else { - // otherwise, check if fsm >= beg & fsm < end - let beg_const = builder.add_constant(beg, fsm_size); - let end_const = builder.add_constant(end, fsm_size); - let beg_guard: ir::Guard = - guard!(fsm["out"] >= beg_const["out"]); - let end_guard: ir::Guard = - guard!(fsm["out"] < end_const["out"]); - Box::new(ir::Guard::And( - Box::new(beg_guard), - Box::new(end_guard), - )) - } - } - } -} - -// Takes in static assignment `assign` and returns a dynamic assignments -// Mainly transforms the guards such that fsm.out >= 2 & fsm.out <= 3 -pub(super) fn make_assign_dyn( - assign: ir::Assignment, - fsm: &ir::RRC, - fsm_size: u64, - builder: &mut ir::Builder, - is_static_comp: bool, - comp_sig: Option>, -) -> ir::Assignment { - ir::Assignment { - src: assign.src, - dst: assign.dst, - attributes: assign.attributes, - guard: make_guard_dyn( - *assign.guard, - fsm, - fsm_size, - builder, - is_static_comp, - comp_sig, - ), - } -} - // 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( @@ -178,8 +52,8 @@ fn find_static_group( // 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}` -// NOTE: assumes that static groups will only write the go holes of other static -// groups, and never dynamic groups +// 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 { @@ -192,86 +66,20 @@ fn get_go_writes(sgroup: &ir::RRC) -> HashSet { } impl CompileStatic { - // returns an "early reset" group based on the information given - // in the arguments. - // sgroup_assigns are the static assignments of the group (they need to be - // changed to dynamic by instantiating an fsm, i.e., %[0,2] -> fsm.out < 2) - // name of early reset group has prefix "early_reset_{sgroup_name}" - fn make_early_reset_group( - &mut self, - sgroup_assigns: &mut Vec>, - sgroup_name: ir::Id, - latency: u64, - attributes: ir::Attributes, - fsm: ir::RRC, - builder: &mut ir::Builder, - ) -> ir::RRC { - let fsm_name = fsm.borrow().name(); - let fsm_size = fsm - .borrow() - .find("out") - .unwrap_or_else(|| unreachable!("no `out` port on {fsm_name}")) - .borrow() - .width; - structure!( builder; - // done hole will be undefined bc of early reset - let ud = prim undef(1); - let signal_on = constant(1,1); - let adder = prim std_add(fsm_size); - let const_one = constant(1, fsm_size); - let first_state = constant(0, fsm_size); - let penultimate_state = constant(latency-1, fsm_size); - ); - // create the dynamic group we will use to replace the static group - let mut early_reset_name = sgroup_name.to_string(); - early_reset_name.insert_str(0, "early_reset_"); - let g = builder.add_group(early_reset_name); - // converting static assignments to dynamic assignments - let mut assigns = sgroup_assigns - .drain(..) - .map(|assign| { - make_assign_dyn(assign, &fsm, fsm_size, builder, false, None) - }) - .collect_vec(); - // assignments to increment the fsm - let not_penultimate_state_guard: ir::Guard = - guard!(fsm["out"] != penultimate_state["out"]); - let penultimate_state_guard: ir::Guard = - guard!(fsm["out"] == penultimate_state["out"]); - let fsm_incr_assigns = build_assignments!( - builder; - // increments the fsm - adder["left"] = ? fsm["out"]; - adder["right"] = ? const_one["out"]; - fsm["write_en"] = ? signal_on["out"]; - fsm["in"] = not_penultimate_state_guard ? adder["out"]; - // resets the fsm early - fsm["in"] = penultimate_state_guard ? first_state["out"]; - // will never reach this guard since we are resetting when we get to - // the penultimate state - g["done"] = ? ud["out"]; - ); - assigns.extend(fsm_incr_assigns.to_vec()); - // maps the "early reset" group name to the "fsm name" that it borrows. - // this is helpful when we build the "wrapper group" - self.fsm_info_map - .insert(g.borrow().name(), (fsm.borrow().name(), fsm_size)); - // adding the assignments to the new dynamic group and creating a - // new (dynamic) enable - g.borrow_mut().assignments = assigns; - g.borrow_mut().attributes = attributes; - g - } - + /// Builds a wrapper group for group named group_name using fsm and + /// and a signal_reg. + /// Both the group and FSM (and the signal_reg) should already exist. + /// `add_resetting_logic` is a bool; since the same FSM/signal_reg pairing + /// may be used for multiple static islands, and we only add resetting logic + /// for the signal_reg once. fn build_wrapper_group( - fsm_name: &ir::Id, - fsm_width: u64, + fsm_object: ir::RRC, group_name: &ir::Id, signal_reg: ir::RRC, builder: &mut ir::Builder, - add_continuous_assigns: bool, + add_reseting_logic: bool, ) -> ir::RRC { - // get the groups/fsm necessary to build the wrapper group + // Get the group and fsm necessary to build the wrapper group. let early_reset_group = builder .component .get_groups() @@ -282,31 +90,22 @@ impl CompileStatic { group_name ) }); - let early_reset_fsm = - builder.component.find_cell(*fsm_name).unwrap_or_else(|| { - unreachable!( - "called build_wrapper_group with {}, which is not an fsm", - fsm_name - ) - }); + // fsm.out == 0 + let first_state = *fsm_object.borrow().query_between(builder, (0, 1)); structure!( builder; - let state_zero = constant(0, fsm_width); let signal_on = constant(1, 1); let signal_off = constant(0, 1); ); - // make guards - // fsm.out == 0 ? - let first_state: ir::Guard = - guard!(early_reset_fsm["out"] == state_zero["out"]); - // signal_reg.out ? + // Making the rest of the guards guards: + // signal_reg.out let signal_reg_guard: ir::Guard = guard!(signal_reg["out"]); - // !signal_reg.out ? + // !signal_reg.out let not_signal_reg = signal_reg_guard.clone().not(); - // fsm.out == 0 & signal_reg.out ? + // fsm.out == 0 & signal_reg.out let first_state_and_signal = first_state.clone() & signal_reg_guard; - // fsm.out == 0 & ! signal_reg.out ? + // fsm.out == 0 & ! signal_reg.out let first_state_and_not_signal = first_state & not_signal_reg; // create the wrapper group for early_reset_group let mut wrapper_name = group_name.clone().to_string(); @@ -322,7 +121,7 @@ impl CompileStatic { // group[done] = fsm.out == 0 & signal_reg.out ? 1'd1 g["done"] = first_state_and_signal ? signal_on["out"]; ); - if add_continuous_assigns { + if add_reseting_logic { // continuous assignments to reset signal_reg back to 0 when the wrapper is done let continuous_assigns = build_assignments!( builder; @@ -339,28 +138,6 @@ impl CompileStatic { g } - 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 - } - /// compile `while` whose body is `static` control such that at the end of each /// iteration, the checking of condition does not incur an extra cycle of /// latency. @@ -371,8 +148,7 @@ impl CompileStatic { /// Note: this only works if the port for the while condition is `@stable`. fn build_wrapper_group_while( &self, - fsm_name: &ir::Id, - fsm_width: u64, + fsm_object: ir::RRC, group_name: &ir::Id, port: RRC, builder: &mut ir::Builder, @@ -386,13 +162,8 @@ impl CompileStatic { group_name ) }); - let early_reset_fsm = - builder.component.find_cell(*fsm_name).unwrap_or_else(|| { - unreachable!( - "called build_wrapper_group with {}, which is not an fsm", - fsm_name - ) - }); + + let fsm_eq_0 = *fsm_object.borrow().query_between(builder, (0, 1)); let wrapper_group = builder.add_group(format!("while_wrapper_{}", group_name)); @@ -400,13 +171,11 @@ impl CompileStatic { structure!( builder; let one = constant(1, 1); - let time_0 = constant(0, fsm_width); ); let port_parent = port.borrow().cell_parent(); let port_name = port.borrow().name; - let done_guard = guard!(port_parent[port_name]).not() - & guard!(early_reset_fsm["out"] == time_0["out"]); + let done_guard = guard!(port_parent[port_name]).not() & fsm_eq_0; let assignments = build_assignments!( builder; @@ -420,6 +189,69 @@ 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( + coloring: HashMap, + mut static_groups: Vec>, + _builder: &mut ir::Builder, + ) -> Vec { + // "reverse" the coloring to map colors -> static group_names + let mut color_to_groups: HashMap> = + HashMap::new(); + for (group, color) in coloring { + color_to_groups.entry(color).or_default().insert(group); + } + // Need deterministic ordering for testing. + let mut vec_color_to_groups: Vec<(ir::Id, HashSet)> = + color_to_groups.into_iter().collect(); + vec_color_to_groups + .sort_by(|(color1, _), (color2, _)| color1.cmp(color2)); + vec_color_to_groups + .into_iter() + .map(|(_, group_names)| { + // For each color, build a StaticSchedule object. + // We first have to figure out out which groups we need to + // build the static_schedule object for. + let (matching_groups, other_groups) = + static_groups.drain(..).partition(|group| { + group_names.contains(&group.borrow().name()) + }); + let sch = StaticSchedule::from(matching_groups); + static_groups = other_groups; + sch + }) + .collect() + } + // 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. @@ -569,47 +401,6 @@ impl CompileStatic { } } - // Given a "coloring" of static group names -> their "colors", - // instantiate one fsm per color and return a hashmap that maps - // fsm names -> groups that it handles - fn build_fsm_mapping( - coloring: HashMap, - static_groups: &[ir::RRC], - builder: &mut ir::Builder, - ) -> HashMap> { - // "reverse" the coloring to map colors -> static group_names - let mut color_to_groups: HashMap> = - HashMap::new(); - for (group, color) in coloring { - color_to_groups.entry(color).or_default().insert(group); - } - // Need deterministic ordering for testing. - let mut vec_color_to_groups: Vec<(ir::Id, HashSet)> = - color_to_groups.into_iter().collect(); - vec_color_to_groups - .sort_by(|(color1, _), (color2, _)| color1.cmp(color2)); - vec_color_to_groups.into_iter().map(|(color, group_names)| { - // For each color, build an FSM that has the number of bits required - // for the largest latency in `group_names` - let max_latency = group_names - .iter() - .map(|g| { - find_static_group(g, static_groups).borrow() - .latency - }) - .max().unwrap_or_else(|| unreachable!("group {color} had no corresponding groups in its coloring map") - ); - let fsm_size = get_bit_width_from( - max_latency + 1, /* represent 0..latency */ - ); - structure!( builder; - let fsm = prim std_reg(fsm_size); - ); - let fsm_name = fsm.borrow().name(); - (fsm_name, group_names) - }).collect() - } - // helper to `build_sgroup_uses_map` // `parent_group` is the group that we are "currently" analyzing // `full_group_ancestry` is the "ancestry of the group we are analyzing" @@ -672,6 +463,177 @@ impl CompileStatic { } } +// These are the functions used to compile for the static *component* interface +impl CompileStatic { + // Used for guards in a one cycle static component. + // Replaces %0 with comp.go. + fn make_guard_dyn_one_cycle_static_comp( + guard: ir::Guard, + comp_sig: RRC, + ) -> ir::Guard { + match guard { + ir::Guard::Or(l, r) => { + let left = + Self::make_guard_dyn_one_cycle_static_comp(*l, Rc::clone(&comp_sig)); + let right = Self::make_guard_dyn_one_cycle_static_comp(*r, Rc::clone(&comp_sig)); + ir::Guard::or(left, right) + } + ir::Guard::And(l, r) => { + let left = Self::make_guard_dyn_one_cycle_static_comp(*l, Rc::clone(&comp_sig)); + let right = Self::make_guard_dyn_one_cycle_static_comp(*r, Rc::clone(&comp_sig)); + ir::Guard::and(left, right) + } + ir::Guard::Not(g) => { + let f = Self::make_guard_dyn_one_cycle_static_comp(*g, Rc::clone(&comp_sig)); + ir::Guard::Not(Box::new(f)) + } + ir::Guard::Info(t) => { + match t.get_interval() { + (0, 1) => guard!(comp_sig["go"]), + _ => unreachable!("This function is implemented for 1 cycle static components, only %0 can exist as timing guard"), + + } + } + ir::Guard::CompOp(op, l, r) => ir::Guard::CompOp(op, l, r), + ir::Guard::Port(p) => ir::Guard::Port(p), + ir::Guard::True => ir::Guard::True, + } + } + + // Used for assignments in a one cycle static component. + // Replaces %0 with comp.go in the assignment's guard. + fn make_assign_dyn_one_cycle_static_comp( + assign: ir::Assignment, + comp_sig: RRC, + ) -> ir::Assignment { + ir::Assignment { + src: assign.src, + dst: assign.dst, + attributes: assign.attributes, + guard: Box::new(Self::make_guard_dyn_one_cycle_static_comp( + *assign.guard, + comp_sig, + )), + } + } + + // Makes `done` signal for promoted static component. + fn make_done_signal_for_promoted_component( + fsm: &StaticFSM, + builder: &mut ir::Builder, + comp_sig: RRC, + ) -> Vec> { + let first_state_guard = *fsm.query_between(builder, (0, 1)); + structure!(builder; + let sig_reg = prim std_reg(1); + let one = constant(1, 1); + let zero = constant(0, 1); + ); + let go_guard = guard!(comp_sig["go"]); + let not_go_guard = !guard!(comp_sig["go"]); + let comp_done_guard = + first_state_guard.clone().and(guard!(sig_reg["out"])); + let assigns = build_assignments!(builder; + // Only write to sig_reg when fsm == 0 + sig_reg["write_en"] = first_state_guard ? one["out"]; + // If fsm == 0 and comp.go is high, it means we are starting an execution, + // so we set signal_reg to high. Note that this happens regardless of + // whether comp.done is high. + sig_reg["in"] = go_guard ? one["out"]; + // Otherwise, we set `sig_reg` to low. + sig_reg["in"] = not_go_guard ? zero["out"]; + // comp.done is high when FSM == 0 and sig_reg is high, + // since that means we have just finished an execution. + comp_sig["done"] = comp_done_guard ? one["out"]; + ); + assigns.to_vec() + } + + // Makes a done signal for a one-cycle static component. + // Essentially you just have to use a one-cycle delay register that + // takes the `go` signal as input. + fn make_done_signal_for_promoted_component_one_cycle( + builder: &mut ir::Builder, + comp_sig: RRC, + ) -> Vec> { + structure!(builder; + let sig_reg = prim std_reg(1); + let one = constant(1, 1); + let zero = constant(0, 1); + ); + let go_guard = guard!(comp_sig["go"]); + let not_go = !guard!(comp_sig["go"]); + let signal_on_guard = guard!(sig_reg["out"]); + let assigns = build_assignments!(builder; + // For one cycle components, comp.done is just whatever comp.go + // was during the previous cycle. + // signal_reg serves as a forwarding register that delays + // the `go` signal for one cycle. + sig_reg["in"] = go_guard ? one["out"]; + sig_reg["in"] = not_go ? zero["out"]; + sig_reg["write_en"] = ? one["out"]; + comp_sig["done"] = signal_on_guard ? one["out"]; + ); + assigns.to_vec() + } + + // Compiles `sgroup` according to the static component interface. + // The assignments are removed from `sgroup` and placed into + // `builder.component`'s continuous assignments. + fn compile_static_interface( + sgroup: ir::RRC, + builder: &mut ir::Builder, + ) { + if sgroup.borrow().get_latency() > 1 { + // 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); + builder + .component + .continuous_assignments + .extend(assigns.pop_front().unwrap()); + let comp_sig = Rc::clone(&builder.component.signature); + if builder.component.attributes.has(ir::BoolAttr::Promoted) { + // If necessary, add the logic to produce a done signal. + let done_assigns = + Self::make_done_signal_for_promoted_component( + &fsm, builder, comp_sig, + ); + builder + .component + .continuous_assignments + .extend(done_assigns); + } + } else { + // Handle components with latency == 1. + // In this case, we don't need an FSM; we just guard the assignments + // with comp.go. + let assignments = + std::mem::take(&mut sgroup.borrow_mut().assignments); + for assign in assignments { + let comp_sig = Rc::clone(&builder.component.signature); + builder.component.continuous_assignments.push( + Self::make_assign_dyn_one_cycle_static_comp( + assign, comp_sig, + ), + ); + } + if builder.component.attributes.has(ir::BoolAttr::Promoted) { + let comp_sig = Rc::clone(&builder.component.signature); + let done_assigns = + Self::make_done_signal_for_promoted_component_one_cycle( + builder, comp_sig, + ); + builder + .component + .continuous_assignments + .extend(done_assigns); + } + } + } +} + impl Visitor for CompileStatic { fn start( &mut self, @@ -679,8 +641,32 @@ impl Visitor for CompileStatic { sigs: &ir::LibrarySignatures, _comps: &[ir::Component], ) -> VisResult { + // Static components have a different interface than static groups. + // If we have a static component, we have to compile the top-level + // island (this island should be a group by now and corresponds + // to the the entire control of the component) differently. + // This island might invoke other static groups-- these static groups + // should still follow the group interface. + let top_level_sgroup = if comp.is_static() { + let comp_control = comp.control.borrow(); + match &*comp_control { + ir::Control::Static(ir::StaticControl::Enable(sen)) => { + Some(sen.group.borrow().name()) + } + _ => return Err(Error::malformed_control(format!("Non-Enable Static Control should have been compiled away. Run {} to do this", crate::passes::StaticInliner::name()))), + } + } else { + None + }; + + // Drain static groups of component let sgroups: Vec> = comp.get_static_groups_mut().drain().collect(); + + // 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); @@ -694,62 +680,111 @@ impl Visitor for CompileStatic { ); Self::add_go_port_conflicts(&sgroup_uses_map, &mut conflict_graph); let coloring = conflict_graph.color_greedy(None, true); + let mut builder = ir::Builder::new(comp, sigs); - // build Mappings of fsm names -> set of groups that it can handle. - let fsm_mappings = - Self::build_fsm_mapping(coloring, &sgroups, &mut builder); - let mut groups_to_fsms = HashMap::new(); - // "Reverses" fsm_mappings to map group names -> fsm cells - for (fsm_name, group_names) in fsm_mappings { - let fsm = builder.component.find_guaranteed_cell(fsm_name); - for group_name in group_names { - groups_to_fsms.insert(group_name, Rc::clone(&fsm)); - } - } + // Build one StaticSchedule object per color + let mut schedule_objects = + Self::build_schedule_objects(coloring, sgroups, &mut builder); + + // Map so we can rewrite `static_group[go]` to `early_reset_group[go]` + let mut group_rewrites = ir::rewriter::PortRewriteMap::default(); + + // Realize an fsm for each StaticSchedule object. + for sch in &mut schedule_objects { + // Check whetehr we are compiling the top level static island. + let static_component_interface = match top_level_sgroup { + None => false, + // For the top level group, sch.static_groups should really only + // have group--the top level group. + Some(top_level_group) => sch + .static_groups + .iter() + .any(|g| g.borrow().name() == top_level_group), + }; + // Static component/groups have different interfaces + if static_component_interface { + // 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( + sch.static_groups.pop().unwrap(), + &mut builder, + ) + } else { + let (mut static_group_assigns, fsm) = sch + .realize_schedule(&mut builder, static_component_interface); + 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. + let static_group_name = static_group.borrow().name(); + let mut early_reset_name = static_group_name.to_string(); + early_reset_name.insert_str(0, "early_reset_"); + let early_reset_group = builder.add_group(early_reset_name); + let mut assigns = static_group_assigns.pop_front().unwrap(); + + // Add assignment `group[done] = ud.out`` to the new group. + structure!( builder; let ud = prim undef(1);); + let early_reset_done_assign = build_assignments!( + builder; + early_reset_group["done"] = ? ud["out"]; + ); + assigns.extend(early_reset_done_assign); - // create "early reset" dynamic groups that never reach set their done hole - for sgroup in sgroups.iter() { - let mut sgroup_ref = sgroup.borrow_mut(); - let sgroup_name = sgroup_ref.name(); - let sgroup_latency = sgroup_ref.get_latency(); - let sgroup_attributes = sgroup_ref.attributes.clone(); - let sgroup_assigns = &mut sgroup_ref.assignments; - let g = self.make_early_reset_group( - sgroup_assigns, - sgroup_name, - sgroup_latency, - sgroup_attributes, - Rc::clone(groups_to_fsms.get(&sgroup_name).unwrap_or_else( - || unreachable!("{sgroup_name} has no corresponding fsm"), - )), - &mut builder, - ); - // map the static group name -> early reset group name - // helpful for rewriting control - self.reset_early_map.insert(sgroup_name, g.borrow().name()); - // group_rewrite_map helps write static_group[go] to early_reset_group[go] - // technically could do this w/ early_reset_map but is easier w/ - // group_rewrite, which is explicitly of type `PortRewriterMap` - self.group_rewrite.insert( - ir::Canonical::new(sgroup_name, ir::Id::from("go")), - g.borrow().find("go").unwrap_or_else(|| { - unreachable!("group {} has no go port", g.borrow().name()) - }), - ); + early_reset_group.borrow_mut().assignments = assigns; + early_reset_group.borrow_mut().attributes = + static_group.borrow().attributes.clone(); + + // Now we have to update the fields with a bunch of information. + // This makes it easier when we have to build wrappers, rewrite ports, etc. + + // Map the static group name -> early reset group name. + // This is helpful for rewriting control + self.reset_early_map.insert( + static_group_name, + early_reset_group.borrow().name(), + ); + // self.group_rewrite_map helps write static_group[go] to early_reset_group[go] + // Technically we could do this w/ early_reset_map but is easier w/ + // group_rewrite, which is explicitly of type `PortRewriterMap` + group_rewrites.insert( + ir::Canonical::new( + static_group_name, + ir::Id::from("go"), + ), + early_reset_group.borrow().find("go").unwrap_or_else( + || { + unreachable!( + "group {} has no go port", + early_reset_group.borrow().name() + ) + }, + ), + ); + + self.fsm_info_map.insert( + early_reset_group.borrow().name(), + Rc::clone(&fsm_ref), + ); + } + } } - // rewrite static_group[go] to early_reset_group[go] + // Rewrite static_group[go] to early_reset_group[go] // don't have to worry about writing static_group[done] b/c static // groups don't have done holes. comp.for_each_assignment(|assign| { assign.for_each_port(|port| { - self.group_rewrite + group_rewrites .get(&port.borrow().canonical()) .map(Rc::clone) }) }); - comp.get_static_groups_mut().append(sgroups.into_iter()); + // Add the static groups back to the component. + for schedule in schedule_objects { + comp.get_static_groups_mut() + .append(schedule.static_groups.into_iter()); + } Ok(Action::Continue) } @@ -762,8 +797,12 @@ impl Visitor for CompileStatic { sigs: &ir::LibrarySignatures, _comps: &[ir::Component], ) -> VisResult { - // assume that there are only static enables left. - // if there are any other type of static control, then error out. + // No need to build wrapper for static component interface + if comp.is_static() { + return Ok(Action::Continue); + } + // 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 { return Err(Error::malformed_control(format!("Non-Enable Static Control should have been compiled away. Run {} to do this", crate::passes::StaticInliner::name()))); }; @@ -775,7 +814,7 @@ impl Visitor for CompileStatic { let early_reset_name = self.reset_early_map.get(&sgroup_name).unwrap_or_else(|| { unreachable!( - "group {} early reset has not been created", + "{}'s early reset group has not been created", sgroup_name ) }); @@ -785,11 +824,12 @@ impl Visitor for CompileStatic { None => { // create the builder/cells that we need to create wrapper group let mut builder = ir::Builder::new(comp, sigs); - let (fsm_name, fsm_width )= self.fsm_info_map.get(early_reset_name).unwrap_or_else(|| unreachable!("group {} has no correspondoing fsm in self.fsm_map", early_reset_name)); + let fsm_object = self.fsm_info_map.get(early_reset_name).unwrap_or_else(|| unreachable!("group {} has no correspondoing fsm in self.fsm_map", early_reset_name)); // If we've already made a wrapper for a group that uses the same // FSM, we can reuse the signal_reg. Otherwise, we must // instantiate a new signal_reg. - let wrapper = match self.signal_reg_map.get(fsm_name) { + let fsm_name = fsm_object.borrow().get_unique_id(); + let wrapper = match self.signal_reg_map.get(&fsm_name) { None => { // Need to build the signal_reg and the continuous // assignment that resets the signal_reg @@ -797,10 +837,9 @@ impl Visitor for CompileStatic { let signal_reg = prim std_reg(1); ); self.signal_reg_map - .insert(*fsm_name, signal_reg.borrow().name()); + .insert(fsm_name, signal_reg.borrow().name()); Self::build_wrapper_group( - fsm_name, - *fsm_width, + Rc::clone(fsm_object), early_reset_name, signal_reg, &mut builder, @@ -818,8 +857,7 @@ impl Visitor for CompileStatic { unreachable!("signal reg {reg_name} found") }); Self::build_wrapper_group( - fsm_name, - *fsm_width, + Rc::clone(fsm_object), early_reset_name, signal_reg, &mut builder, @@ -840,7 +878,7 @@ impl Visitor for CompileStatic { Ok(Action::Change(Box::new(e))) } - /// if while body is static, then we want to make sure that the while + /// If while body is static, then we want to make sure that the while /// body does not take the extra cycle incurred by the done condition /// So we replace the while loop with `enable` of a wrapper group /// that sets the go signal of the static group in the while loop body high @@ -891,10 +929,9 @@ impl Visitor for CompileStatic { let reset_group_name = self.get_reset_group_name(sc); // Get fsm for reset_group - let (fsm, fsm_width) = self.fsm_info_map.get(reset_group_name).unwrap_or_else(|| unreachable!("group {} has no correspondoing fsm in self.fsm_map", reset_group_name)); + let fsm_object = self.fsm_info_map.get(reset_group_name).unwrap_or_else(|| unreachable!("group {} has no correspondoing fsm in self.fsm_map", reset_group_name)); let wrapper_group = self.build_wrapper_group_while( - fsm, - *fsm_width, + Rc::clone(fsm_object), reset_group_name, Rc::clone(&s.port), &mut builder, @@ -917,11 +954,17 @@ impl Visitor for CompileStatic { // we should have already drained the assignments in static groups for g in comp.get_static_groups() { if !g.borrow().assignments.is_empty() { - unreachable!("Should have converted all static groups to dynamic. {} still has assignments in it", g.borrow().name()); + unreachable!("Should have converted all static groups to dynamic. {} still has assignments in it. It's possible that you may need to run {} to remove dead groups and get rid of this error.", g.borrow().name(), crate::passes::DeadGroupRemoval::name()); } } // remove all static groups comp.get_static_groups_mut().retain(|_| false); + + // Remove control if static component + if comp.is_static() { + comp.control = ir::rrc(ir::Control::empty()) + } + Ok(Action::Continue) } } diff --git a/calyx-opt/src/passes/compile_static_interface.rs b/calyx-opt/src/passes/compile_static_interface.rs deleted file mode 100644 index 3a0d47764d..0000000000 --- a/calyx-opt/src/passes/compile_static_interface.rs +++ /dev/null @@ -1,360 +0,0 @@ -use super::compile_static::make_assign_dyn; -use crate::passes::math_utilities::get_bit_width_from; -use crate::traversal::{Action, Named, VisResult, Visitor}; -use calyx_ir as ir; -use ir::{ - build_assignments, guard, structure, Attributes, Guard, Nothing, - StaticTiming, RRC, -}; -use itertools::Itertools; -use std::cell::RefCell; -use std::rc::Rc; - -#[derive(Default)] -pub struct CompileStaticInterface; - -impl Named for CompileStaticInterface { - fn name() -> &'static str { - "compile-static-interface" - } - - fn description() -> &'static str { - "Compiles Static Component Interface" - } -} - -// Looks recursively thru guard to %[0:n] into %0 | %[1:n]. -fn separate_first_cycle( - guard: ir::Guard, -) -> ir::Guard { - match guard { - ir::Guard::Info(st) => { - let (beg, end) = st.get_interval(); - if beg == 0 && end != 1 { - let first_cycle = - ir::Guard::Info(ir::StaticTiming::new((0, 1))); - let after = ir::Guard::Info(ir::StaticTiming::new((1, end))); - let cong = ir::Guard::or(first_cycle, after); - return cong; - } - guard - } - ir::Guard::And(l, r) => { - let left = separate_first_cycle(*l); - let right = separate_first_cycle(*r); - ir::Guard::and(left, right) - } - ir::Guard::Or(l, r) => { - let left = separate_first_cycle(*l); - let right = separate_first_cycle(*r); - ir::Guard::or(left, right) - } - ir::Guard::Not(g) => { - let a = separate_first_cycle(*g); - ir::Guard::Not(Box::new(a)) - } - _ => guard, - } -} - -// Looks recursively thru assignment's guard to %[0:n] into %0 | %[1:n]. -fn separate_first_cycle_assign( - assign: ir::Assignment, -) -> ir::Assignment { - ir::Assignment { - src: assign.src, - dst: assign.dst, - attributes: assign.attributes, - guard: Box::new(separate_first_cycle(*assign.guard)), - } -} - -// Used for guards in a one cycle static component. -// Replaces %0 with comp.go. -fn make_guard_dyn_one_cycle_static_comp( - guard: ir::Guard, - comp_sig: RRC, -) -> ir::Guard { - match guard { - ir::Guard::Or(l, r) => { - let left = - make_guard_dyn_one_cycle_static_comp(*l, Rc::clone(&comp_sig)); - let right = - make_guard_dyn_one_cycle_static_comp(*r, Rc::clone(&comp_sig)); - ir::Guard::or(left, right) - } - ir::Guard::And(l, r) => { - let left = - make_guard_dyn_one_cycle_static_comp(*l, Rc::clone(&comp_sig)); - let right = - make_guard_dyn_one_cycle_static_comp(*r, Rc::clone(&comp_sig)); - ir::Guard::and(left, right) - } - ir::Guard::Not(g) => { - let f = - make_guard_dyn_one_cycle_static_comp(*g, Rc::clone(&comp_sig)); - ir::Guard::Not(Box::new(f)) - } - ir::Guard::Info(t) => { - match t.get_interval() { - (0, 1) => guard!(comp_sig["go"]), - _ => unreachable!("This function is implemented for 1 cycle static components, only %0 can exist as timing guard"), - - } - } - ir::Guard::CompOp(op, l, r) => ir::Guard::CompOp(op, l, r), - ir::Guard::Port(p) => ir::Guard::Port(p), - ir::Guard::True => ir::Guard::True, - } -} - -// Used for assignments in a one cycle static component. -// Replaces %0 with comp.go in the assignment's guard. -fn make_assign_dyn_one_cycle_static_comp( - assign: ir::Assignment, - comp_sig: RRC, -) -> ir::Assignment { - ir::Assignment { - src: assign.src, - dst: assign.dst, - attributes: assign.attributes, - guard: Box::new(make_guard_dyn_one_cycle_static_comp( - *assign.guard, - comp_sig, - )), - } -} - -impl CompileStaticInterface { - // Takes the assignments within a static component, and instantiates - // an FSM (i.e., counter) to convert %[i:j] into i<= fsm < j. - // Also includes logic to make fsm reset to 0 once it gets to n-1. - fn make_early_reset_assigns_static_component( - &mut self, - sgroup_assigns: &mut Vec>, - latency: u64, - fsm: ir::RRC, - builder: &mut ir::Builder, - comp_sig: RRC, - ) -> Vec> { - let fsm_name = fsm.borrow().name(); - let fsm_size = fsm - .borrow() - .find("out") - .unwrap_or_else(|| unreachable!("no `out` port on {fsm_name}")) - .borrow() - .width; - let mut assigns = sgroup_assigns - .drain(..) - .map(separate_first_cycle_assign) - .collect_vec(); - let mut dyn_assigns = assigns - .drain(..) - .map(|assign| { - make_assign_dyn( - assign, - &fsm, - fsm_size, - builder, - true, - Some(Rc::clone(&comp_sig)), - ) - }) - .collect_vec(); - let this = Rc::clone(&comp_sig); - structure!( builder; - // done hole will be undefined bc of early reset - let signal_on = constant(1,1); - let adder = prim std_add(fsm_size); - let const_one = constant(1, fsm_size); - let first_state = constant(0, fsm_size); - let final_state = constant(latency-1, fsm_size); - ); - let g1: Guard = guard!(this["go"]); - let g2: Guard = guard!(fsm["out"] == first_state["out"]); - let trigger_guard = ir::Guard::and(g1, g2); - let g3: Guard = guard!(fsm["out"] != first_state["out"]); - let g4: Guard = guard!(fsm["out"] != final_state["out"]); - let incr_guard = ir::Guard::and(g3, g4); - let stop_guard: Guard = - guard!(fsm["out"] == final_state["out"]); - let fsm_incr_assigns = build_assignments!( - builder; - // Incrementsthe fsm - adder["left"] = ? fsm["out"]; - adder["right"] = ? const_one["out"]; - // Always write into fsm. - fsm["write_en"] = ? signal_on["out"]; - // If fsm == 0 and comp.go is high, then we start an execution. - fsm["in"] = trigger_guard ? const_one["out"]; - // If 1 < fsm < n - 1, then we unconditionally increment the fsm. - fsm["in"] = incr_guard ? adder["out"]; - // If fsm == n -1 , then we reset the FSM. - fsm["in"] = stop_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. - ); - dyn_assigns.extend(fsm_incr_assigns); - - dyn_assigns - } - - // Makes `done` signal for promoted static component. - fn make_done_signal_for_promoted_component( - &mut self, - fsm: ir::RRC, - builder: &mut ir::Builder, - comp_sig: RRC, - ) -> Vec> { - let fsm_size = fsm - .borrow() - .find("out") - .unwrap_or_else(|| { - unreachable!("no `out` port on {}", fsm.borrow().name()) - }) - .borrow() - .width; - structure!(builder; - let sig_reg = prim std_reg(1); - let one = constant(1, 1); - let zero = constant(0, 1); - let first_state = constant(0, fsm_size); - ); - let go_guard = guard!(comp_sig["go"]); - let not_go_guard = !guard!(comp_sig["go"]); - let first_state_guard = guard!(fsm["out"] == first_state["out"]); - let comp_done_guard = - guard!(fsm["out"] == first_state["out"]) & guard!(sig_reg["out"]); - let assigns = build_assignments!(builder; - // Only write to sig_reg when fsm == 0 - sig_reg["write_en"] = first_state_guard ? one["out"]; - // If fsm == 0 and comp.go is high, it means we are starting an execution, - // so we set signal_reg to high. Note that this happens regardless of - // whether comp.done is high. - sig_reg["in"] = go_guard ? one["out"]; - // Otherwise, we set `sig_reg` to low. - sig_reg["in"] = not_go_guard ? zero["out"]; - // comp.done is high when FSM == 0 and sig_reg is high, - // since that means we have just finished an execution. - comp_sig["done"] = comp_done_guard ? one["out"]; - ); - assigns.to_vec() - } - - fn make_done_signal_for_promoted_component_one_cycle( - &mut self, - builder: &mut ir::Builder, - comp_sig: RRC, - ) -> Vec> { - structure!(builder; - let sig_reg = prim std_reg(1); - let one = constant(1, 1); - let zero = constant(0, 1); - ); - let go_guard = guard!(comp_sig["go"]); - let not_go = !guard!(comp_sig["go"]); - let signal_on_guard = guard!(sig_reg["out"]); - let assigns = build_assignments!(builder; - // For one cycle components, comp.done is just whatever comp.go - // was during the previous cycle. - // signal_reg serves as a forwarding register that delays - // the `go` signal for one cycle. - sig_reg["in"] = go_guard ? one["out"]; - sig_reg["in"] = not_go ? zero["out"]; - sig_reg["write_en"] = ? one["out"]; - comp_sig["done"] = signal_on_guard ? one["out"]; - ); - assigns.to_vec() - } -} - -impl Visitor for CompileStaticInterface { - fn start_static_control( - &mut self, - s: &mut ir::StaticControl, - comp: &mut ir::Component, - sigs: &ir::LibrarySignatures, - _comps: &[ir::Component], - ) -> VisResult { - if comp.is_static() && s.get_latency() > 1 { - // Handle components with latency > 1. - let latency = s.get_latency(); - if let ir::StaticControl::Enable(sen) = s { - let mut builder = ir::Builder::new(comp, sigs); - let fsm_size = get_bit_width_from(latency + 1); - structure!( builder; - let fsm = prim std_reg(fsm_size); - ); - let mut assignments = - std::mem::take(&mut sen.group.borrow_mut().assignments); - let comp_sig = Rc::clone(&builder.component.signature); - let dyn_assigns = self - .make_early_reset_assigns_static_component( - &mut assignments, - s.get_latency(), - Rc::clone(&fsm), - &mut builder, - Rc::clone(&comp_sig), - ); - builder.component.continuous_assignments.extend(dyn_assigns); - if builder.component.attributes.has(ir::BoolAttr::Promoted) { - let done_assigns = self - .make_done_signal_for_promoted_component( - Rc::clone(&fsm), - &mut builder, - Rc::clone(&comp_sig), - ); - builder - .component - .continuous_assignments - .extend(done_assigns); - } - } - } else if comp.is_static() && s.get_latency() == 1 { - // Handle components with latency == 1. - if let ir::StaticControl::Enable(sen) = s { - let assignments = - std::mem::take(&mut sen.group.borrow_mut().assignments); - for assign in assignments { - let comp_sig = Rc::clone(&comp.signature); - comp.continuous_assignments.push( - make_assign_dyn_one_cycle_static_comp(assign, comp_sig), - ); - } - if comp.attributes.has(ir::BoolAttr::Promoted) { - let mut builder = ir::Builder::new(comp, sigs); - let comp_sig = Rc::clone(&builder.component.signature); - let done_assigns = self - .make_done_signal_for_promoted_component_one_cycle( - &mut builder, - comp_sig, - ); - builder - .component - .continuous_assignments - .extend(done_assigns); - } - } - } - Ok(Action::Continue) - } - - fn finish( - &mut self, - comp: &mut ir::Component, - _sigs: &ir::LibrarySignatures, - _comps: &[ir::Component], - ) -> VisResult { - // Remove the control. - if comp.is_static() { - let _c = std::mem::replace( - &mut comp.control, - Rc::new(RefCell::new(ir::Control::Empty(ir::Empty { - attributes: Attributes::default(), - }))), - ); - } - Ok(Action::Stop) - } -} diff --git a/calyx-opt/src/passes/mod.rs b/calyx-opt/src/passes/mod.rs index 8118d13c38..00e5466abc 100644 --- a/calyx-opt/src/passes/mod.rs +++ b/calyx-opt/src/passes/mod.rs @@ -20,7 +20,7 @@ mod group_to_seq; mod hole_inliner; mod infer_share; mod lower_guards; -mod math_utilities; +pub mod math_utilities; mod merge_assign; mod papercut; mod par_to_seq; @@ -34,7 +34,6 @@ mod static_promotion; mod sync; // mod simplify_guards; mod add_guard; -mod compile_static_interface; mod data_path_infer; mod default_assigns; mod discover_external; @@ -68,6 +67,7 @@ pub use group_to_seq::GroupToSeq; pub use hole_inliner::HoleInliner; pub use infer_share::InferShare; pub use lower_guards::LowerGuards; +pub use math_utilities::get_bit_width_from; pub use merge_assign::MergeAssign; pub use papercut::Papercut; pub use par_to_seq::ParToSeq; @@ -83,7 +83,6 @@ pub use sync::CompileSync; pub use sync::CompileSyncWithoutSyncReg; // pub use simplify_guards::SimplifyGuards; pub use add_guard::AddGuard; -pub use compile_static_interface::CompileStaticInterface; pub use default_assigns::DefaultAssigns; pub use synthesis_papercut::SynthesisPapercut; pub use top_down_compile_control::TopDownCompileControl; diff --git a/examples/futil/dot-product.expect b/examples/futil/dot-product.expect index dd5edecb83..f9eaec7a73 100644 --- a/examples/futil/dot-product.expect +++ b/examples/futil/dot-product.expect @@ -19,10 +19,10 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { @external @data v0 = comb_mem_d1(32, 1, 1); @generated comb_reg = std_reg(1); @generated fsm = std_reg(4); - @generated ud = undef(1); @generated adder = std_add(4); - @generated ud0 = undef(1); @generated adder0 = std_add(4); + @generated ud = undef(1); + @generated ud0 = undef(1); @generated signal_reg = std_reg(1); @generated fsm0 = std_reg(2); @generated invoke0_go = std_wire(1); diff --git a/examples/futil/simple.expect b/examples/futil/simple.expect index ad0210d99e..1dd0c5ad00 100644 --- a/examples/futil/simple.expect +++ b/examples/futil/simple.expect @@ -4,8 +4,8 @@ import "primitives/binary_operators.futil"; component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { cells { @generated fsm = std_reg(3); - @generated ud = undef(1); @generated adder = std_add(3); + @generated ud = undef(1); @generated signal_reg = std_reg(1); @generated early_reset_static_seq_go = std_wire(1); @generated early_reset_static_seq_done = std_wire(1); diff --git a/examples/futil/vectorized-add.expect b/examples/futil/vectorized-add.expect index 52171e14b4..d8d8c4e602 100644 --- a/examples/futil/vectorized-add.expect +++ b/examples/futil/vectorized-add.expect @@ -16,10 +16,10 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { @control le0 = std_le(4); @generated comb_reg = std_reg(1); @generated fsm = std_reg(3); - @generated ud = undef(1); @generated adder = std_add(3); - @generated ud0 = undef(1); @generated adder0 = std_add(3); + @generated ud = undef(1); + @generated ud0 = undef(1); @generated signal_reg = std_reg(1); @generated fsm0 = std_reg(2); @generated invoke0_go = std_wire(1); 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 1e5f4db955..54808b17a6 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 @@ -6,8 +6,6 @@ static<1> component do_add(left: 32, right: 32, @go go: 1, @clk clk: 1, @reset r r = std_reg(32); } wires { - static<1> group a { - } r.write_en = go ? 1'd1; add.right = go ? right; add.left = go ? left; @@ -18,15 +16,33 @@ static<1> component do_add(left: 32, right: 32, @go go: 1, @clk clk: 1, @reset r component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { cells { a = do_add(); + @generated fsm = std_reg(1); + @generated adder = std_add(1); + @generated ud = undef(1); + @generated signal_reg = std_reg(1); } wires { - static<1> group static_invoke { - a.go = %0 ? 1'd1; + group early_reset_static_invoke { + a.go = fsm.out == 1'd0 ? 1'd1; a.left = 32'd5; a.right = 32'd6; + 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 ? 1'd0; + early_reset_static_invoke[done] = ud.out; + } + group wrapper_early_reset_static_invoke { + early_reset_static_invoke[go] = 1'd1; + signal_reg.write_en = fsm.out == 1'd0 & !signal_reg.out ? 1'd1; + signal_reg.in = fsm.out == 1'd0 & !signal_reg.out ? 1'd1; + wrapper_early_reset_static_invoke[done] = fsm.out == 1'd0 & signal_reg.out ? 1'd1; } + signal_reg.write_en = fsm.out == 1'd0 & signal_reg.out ? 1'd1; + signal_reg.in = fsm.out == 1'd0 & signal_reg.out ? 1'd0; } control { - static_invoke; + wrapper_early_reset_static_invoke; } } diff --git a/tests/passes/compile-static-interface/compile-static-interface-one-cycle.futil b/tests/passes/compile-static-interface/compile-static-interface-one-cycle.futil index f88fff692e..72b59836da 100644 --- a/tests/passes/compile-static-interface/compile-static-interface-one-cycle.futil +++ b/tests/passes/compile-static-interface/compile-static-interface-one-cycle.futil @@ -1,4 +1,4 @@ -// -p validate -p compile-invoke -p static-inline -p add-guard -p compile-static-interface +// -p validate -p compile-invoke -p static-inline -p add-guard -p compile-static import "primitives/core.futil"; import "primitives/memories/comb.futil"; 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 0fe8e69443..80b8d8e552 100644 --- a/tests/passes/compile-static-interface/compile-static-interface-repeat.expect +++ b/tests/passes/compile-static-interface/compile-static-interface-repeat.expect @@ -6,30 +6,28 @@ static<6> component do_add(left: 32, right: 32, @go go: 1, @clk clk: 1, @reset r r = std_reg(32); @generated fsm = std_reg(3); @generated adder = std_add(3); + @generated fsm0 = std_reg(1); + @generated adder0 = std_add(1); + @generated ud = undef(1); } wires { - static<1> group a { + group early_reset_a { r.write_en = 1'd1; add.right = right; add.left = r.out; r.in = add.out; - } - static<1> group b { - r.write_en = 1'd1; - add.right = right; - add.left = left; - r.in = add.out; - } - static<6> group static_seq { - } - static<5> group static_repeat { - a[go] = 1'd1; + 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 ? 1'd0; + early_reset_a[done] = ud.out; } r.write_en = go & fsm.out == 3'd0 ? 1'd1; add.right = go & fsm.out == 3'd0 ? right; add.left = go & fsm.out == 3'd0 ? left; r.in = go & fsm.out == 3'd0 ? add.out; - a[go] = fsm.out >= 3'd1 & fsm.out < 3'd6 ? 1'd1; + early_reset_a[go] = fsm.out >= 3'd1 & fsm.out < 3'd6 ? 1'd1; adder.left = fsm.out; adder.right = 3'd1; fsm.write_en = 1'd1; @@ -42,15 +40,33 @@ static<6> component do_add(left: 32, right: 32, @go go: 1, @clk clk: 1, @reset r component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { cells { a = do_add(); + @generated fsm = std_reg(3); + @generated adder = std_add(3); + @generated ud = undef(1); + @generated signal_reg = std_reg(1); } wires { - static<6> group static_invoke { - a.go = %0 ? 1'd1; + group early_reset_static_invoke { + a.go = fsm.out == 3'd0 ? 1'd1; a.left = 32'd5; a.right = 32'd6; + 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 ? 3'd0; + early_reset_static_invoke[done] = ud.out; + } + group wrapper_early_reset_static_invoke { + early_reset_static_invoke[go] = 1'd1; + signal_reg.write_en = fsm.out == 3'd0 & !signal_reg.out ? 1'd1; + signal_reg.in = fsm.out == 3'd0 & !signal_reg.out ? 1'd1; + wrapper_early_reset_static_invoke[done] = fsm.out == 3'd0 & signal_reg.out ? 1'd1; } + signal_reg.write_en = fsm.out == 3'd0 & signal_reg.out ? 1'd1; + signal_reg.in = fsm.out == 3'd0 & signal_reg.out ? 1'd0; } control { - static_invoke; + wrapper_early_reset_static_invoke; } } diff --git a/tests/passes/compile-static-interface/compile-static-interface-repeat.futil b/tests/passes/compile-static-interface/compile-static-interface-repeat.futil index a6a0e8ba05..999c4759b0 100644 --- a/tests/passes/compile-static-interface/compile-static-interface-repeat.futil +++ b/tests/passes/compile-static-interface/compile-static-interface-repeat.futil @@ -1,4 +1,4 @@ -// -p validate -p compile-invoke -p static-inline -p add-guard -p simplify-static-guards -p compile-static-interface +// -p validate -p compile-invoke -p static-inline -p dead-group-removal -p add-guard -p simplify-static-guards -p compile-static import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/compile-static-interface/compile-static-interface.expect b/tests/passes/compile-static-interface/compile-static-interface.expect index b80ce81c65..03aa4c5e00 100644 --- a/tests/passes/compile-static-interface/compile-static-interface.expect +++ b/tests/passes/compile-static-interface/compile-static-interface.expect @@ -8,20 +8,6 @@ static<2> component do_add(left: 32, right: 32, @go go: 1, @clk clk: 1, @reset r @generated adder = std_add(2); } wires { - static<1> group a { - r.write_en = 1'd1; - add.right = right; - add.left = left; - r.in = add.out; - } - static<1> group b { - r.write_en = 1'd1; - add.right = right; - add.left = r.out; - r.in = add.out; - } - static<2> group static_seq { - } r.write_en = go & fsm.out == 2'd0 ? 1'd1; add.right = go & fsm.out == 2'd0 ? right; add.left = go & fsm.out == 2'd0 ? left; @@ -42,15 +28,33 @@ static<2> component do_add(left: 32, right: 32, @go go: 1, @clk clk: 1, @reset r component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { cells { a = do_add(); + @generated fsm = std_reg(2); + @generated adder = std_add(2); + @generated ud = undef(1); + @generated signal_reg = std_reg(1); } wires { - static<2> group static_invoke { - a.go = %0 ? 1'd1; + group early_reset_static_invoke { + a.go = fsm.out == 2'd0 ? 1'd1; a.left = 32'd5; a.right = 32'd6; + 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 ? 2'd0; + early_reset_static_invoke[done] = ud.out; + } + group wrapper_early_reset_static_invoke { + early_reset_static_invoke[go] = 1'd1; + signal_reg.write_en = fsm.out == 2'd0 & !signal_reg.out ? 1'd1; + signal_reg.in = fsm.out == 2'd0 & !signal_reg.out ? 1'd1; + wrapper_early_reset_static_invoke[done] = fsm.out == 2'd0 & signal_reg.out ? 1'd1; } + signal_reg.write_en = fsm.out == 2'd0 & signal_reg.out ? 1'd1; + signal_reg.in = fsm.out == 2'd0 & signal_reg.out ? 1'd0; } control { - static_invoke; + wrapper_early_reset_static_invoke; } } diff --git a/tests/passes/compile-static-interface/compile-static-interface.futil b/tests/passes/compile-static-interface/compile-static-interface.futil index d3086d7958..7993cfe900 100644 --- a/tests/passes/compile-static-interface/compile-static-interface.futil +++ b/tests/passes/compile-static-interface/compile-static-interface.futil @@ -1,4 +1,4 @@ -// -p validate -p compile-invoke -p static-inline -p add-guard -p simplify-static-guards -p compile-static-interface +// -p validate -p compile-invoke -p static-inline -p dead-group-removal -p add-guard -p simplify-static-guards -p compile-static import "primitives/core.futil"; import "primitives/memories/comb.futil"; diff --git a/tests/passes/compile-static/interface-and-cs.expect b/tests/passes/compile-static/interface-and-cs.expect new file mode 100644 index 0000000000..80b8d8e552 --- /dev/null +++ b/tests/passes/compile-static/interface-and-cs.expect @@ -0,0 +1,72 @@ +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; +static<6> component do_add(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 = std_reg(3); + @generated adder = std_add(3); + @generated fsm0 = std_reg(1); + @generated adder0 = std_add(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; + 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 ? 1'd0; + early_reset_a[done] = ud.out; + } + r.write_en = go & fsm.out == 3'd0 ? 1'd1; + add.right = go & fsm.out == 3'd0 ? right; + add.left = go & fsm.out == 3'd0 ? left; + r.in = go & fsm.out == 3'd0 ? add.out; + early_reset_a[go] = fsm.out >= 3'd1 & fsm.out < 3'd6 ? 1'd1; + 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 = fsm.out == 3'd5 ? 3'd0; + } + control {} +} +component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { + cells { + a = do_add(); + @generated fsm = std_reg(3); + @generated adder = std_add(3); + @generated ud = undef(1); + @generated signal_reg = std_reg(1); + } + wires { + group early_reset_static_invoke { + a.go = fsm.out == 3'd0 ? 1'd1; + a.left = 32'd5; + a.right = 32'd6; + 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 ? 3'd0; + early_reset_static_invoke[done] = ud.out; + } + group wrapper_early_reset_static_invoke { + early_reset_static_invoke[go] = 1'd1; + signal_reg.write_en = fsm.out == 3'd0 & !signal_reg.out ? 1'd1; + signal_reg.in = fsm.out == 3'd0 & !signal_reg.out ? 1'd1; + wrapper_early_reset_static_invoke[done] = fsm.out == 3'd0 & signal_reg.out ? 1'd1; + } + signal_reg.write_en = fsm.out == 3'd0 & signal_reg.out ? 1'd1; + signal_reg.in = fsm.out == 3'd0 & signal_reg.out ? 1'd0; + } + control { + wrapper_early_reset_static_invoke; + } +} diff --git a/tests/passes/compile-static/interface-and-cs.futil b/tests/passes/compile-static/interface-and-cs.futil new file mode 100644 index 0000000000..1d14482893 --- /dev/null +++ b/tests/passes/compile-static/interface-and-cs.futil @@ -0,0 +1,49 @@ +// -p validate -p compile-invoke -p static-inline -p dead-group-removal -p add-guard -p simplify-static-guards -p compile-static +// This test case is needed to test how the `compile-static` pass works in tandem +// with components that have been compiled using the static interface (i.e., +// components that separated %[0:n] -> %[0] | %[1:n]). +// Previous test cases had missed this interaction leading us to catching bugs late. +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; + +static<6> component do_add(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/rewrite-group-go.expect b/tests/passes/compile-static/rewrite-group-go.expect index 2cf93b9b80..d3ec1c6fd7 100644 --- a/tests/passes/compile-static/rewrite-group-go.expect +++ b/tests/passes/compile-static/rewrite-group-go.expect @@ -7,11 +7,11 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { b = std_reg(2); c = std_reg(2); @generated fsm = std_reg(2); - @generated fsm0 = std_reg(3); - @generated ud = undef(1); @generated adder = std_add(2); - @generated ud0 = undef(1); + @generated ud = undef(1); + @generated fsm0 = std_reg(3); @generated adder0 = std_add(3); + @generated ud0 = undef(1); @generated signal_reg = std_reg(1); } wires { diff --git a/tests/passes/compile-static/rewrite-static-while-nested.expect b/tests/passes/compile-static/rewrite-static-while-nested.expect index d1409b17d8..466872d406 100644 --- a/tests/passes/compile-static/rewrite-static-while-nested.expect +++ b/tests/passes/compile-static/rewrite-static-while-nested.expect @@ -10,12 +10,12 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { l2 = std_lt(3); r_cond = std_reg(1); @generated fsm = std_reg(2); - @generated ud = undef(1); @generated adder = std_add(2); - @generated ud0 = undef(1); @generated adder0 = std_add(2); - @generated ud1 = undef(1); @generated adder1 = std_add(2); + @generated ud = undef(1); + @generated ud0 = undef(1); + @generated ud1 = undef(1); @generated signal_reg = std_reg(1); } wires { diff --git a/tests/passes/compile-static/rewrite-static-while.expect b/tests/passes/compile-static/rewrite-static-while.expect index d16b503144..10a3529b4b 100644 --- a/tests/passes/compile-static/rewrite-static-while.expect +++ b/tests/passes/compile-static/rewrite-static-while.expect @@ -8,12 +8,12 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { l = std_lt(3); r = std_reg(1); @generated fsm = std_reg(2); - @generated ud = undef(1); @generated adder = std_add(2); - @generated ud0 = undef(1); @generated adder0 = std_add(2); - @generated ud1 = undef(1); @generated adder1 = std_add(2); + @generated ud = undef(1); + @generated ud0 = undef(1); + @generated ud1 = undef(1); @generated signal_reg = std_reg(1); } wires { diff --git a/tests/passes/compile-static/separate-fsms.expect b/tests/passes/compile-static/separate-fsms.expect index 7c95cc05a8..9082db0059 100644 --- a/tests/passes/compile-static/separate-fsms.expect +++ b/tests/passes/compile-static/separate-fsms.expect @@ -8,34 +8,34 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { c = std_reg(2); d = std_reg(2); @generated fsm = std_reg(1); - @generated fsm0 = std_reg(1); - @generated fsm1 = std_reg(1); - @generated fsm2 = std_reg(1); - @generated fsm3 = std_reg(3); - @generated ud = undef(1); @generated adder = std_add(1); - @generated ud0 = undef(1); + @generated ud = undef(1); + @generated fsm0 = std_reg(1); @generated adder0 = std_add(1); - @generated ud1 = undef(1); + @generated ud0 = undef(1); + @generated fsm1 = std_reg(1); @generated adder1 = std_add(1); - @generated ud2 = undef(1); + @generated ud1 = undef(1); + @generated fsm2 = std_reg(1); @generated adder2 = std_add(1); - @generated ud3 = undef(1); + @generated ud2 = undef(1); + @generated fsm3 = std_reg(3); @generated adder3 = std_add(3); + @generated ud3 = undef(1); @generated signal_reg = std_reg(1); @generated signal_reg0 = std_reg(1); @generated signal_reg1 = std_reg(1); } wires { - group early_reset_B { - b.in = 2'd0; - b.write_en = fsm2.out == 1'd0 ? 1'd1; - adder.left = fsm2.out; + group early_reset_A { + a.in = 2'd0; + a.write_en = fsm.out == 1'd0 ? 1'd1; + adder.left = fsm.out; adder.right = 1'd1; - fsm2.write_en = 1'd1; - fsm2.in = fsm2.out != 1'd0 ? adder.out; - fsm2.in = fsm2.out == 1'd0 ? 1'd0; - early_reset_B[done] = ud.out; + fsm.write_en = 1'd1; + fsm.in = fsm.out != 1'd0 ? adder.out; + fsm.in = fsm.out == 1'd0 ? 1'd0; + early_reset_A[done] = ud.out; } group early_reset_C { c.in = 2'd0; @@ -47,25 +47,25 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { fsm0.in = fsm0.out == 1'd0 ? 1'd0; early_reset_C[done] = ud0.out; } - group early_reset_A { - a.in = 2'd0; - a.write_en = fsm.out == 1'd0 ? 1'd1; - adder1.left = fsm.out; - adder1.right = 1'd1; - fsm.write_en = 1'd1; - fsm.in = fsm.out != 1'd0 ? adder1.out; - fsm.in = fsm.out == 1'd0 ? 1'd0; - early_reset_A[done] = ud1.out; - } group early_reset_D { d.in = 2'd0; d.write_en = fsm1.out == 1'd0 ? 1'd1; - adder2.left = fsm1.out; - adder2.right = 1'd1; + adder1.left = fsm1.out; + adder1.right = 1'd1; fsm1.write_en = 1'd1; - fsm1.in = fsm1.out != 1'd0 ? adder2.out; + fsm1.in = fsm1.out != 1'd0 ? adder1.out; fsm1.in = fsm1.out == 1'd0 ? 1'd0; - early_reset_D[done] = ud2.out; + early_reset_D[done] = ud1.out; + } + group early_reset_B { + b.in = 2'd0; + b.write_en = fsm2.out == 1'd0 ? 1'd1; + 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 ? 1'd0; + early_reset_B[done] = ud2.out; } group early_reset_run_A_and_D { early_reset_A[go] = fsm3.out < 3'd4 ? 1'd1;