diff --git a/calyx-ir/src/guard.rs b/calyx-ir/src/guard.rs index 691310f31..f5f3dbcb3 100644 --- a/calyx-ir/src/guard.rs +++ b/calyx-ir/src/guard.rs @@ -506,7 +506,7 @@ impl Guard { } Self::Info(static_timing) => { let (b, e) = static_timing.interval; - (b..=e).collect() + (b..e).collect() } Self::CompOp(..) | Self::Port(_) => HashSet::new(), } diff --git a/calyx-ir/src/structure.rs b/calyx-ir/src/structure.rs index 2164cd805..5489ba129 100644 --- a/calyx-ir/src/structure.rs +++ b/calyx-ir/src/structure.rs @@ -569,11 +569,11 @@ impl Assignment { .chain(std::iter::once(Rc::clone(&self.src))) } - pub fn and_guard(&mut self, addition_opt: Option>) + pub fn and_guard(&mut self, addition: Guard) where T: Eq, { - if let Some(addition) = addition_opt { + if !(addition.is_true()) { self.guard.update(|g| g.and(addition)); } } @@ -616,7 +616,7 @@ impl Assignment { } } -#[derive(Debug)] +#[derive(Debug, Clone)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] pub enum Transition { Unconditional(u64), @@ -831,7 +831,6 @@ impl CombGroup { pub struct FSM { /// Name of this construct pub(super) name: Id, - /// Attributes for this FSM pub attributes: Attributes, /// State indexes into assignments that are supposed to be enabled at that state @@ -881,6 +880,18 @@ impl FSM { }) } + /// Extend the FSM with new transitions and assignments. Will panic if + /// the lengths are not consistent. + pub fn extend_fsm(&mut self, assigns: A, transitions: T) + where + A: IntoIterator>>, + T: IntoIterator, + { + self.assignments.extend(assigns); + self.transitions.extend(transitions); + } + + /// Extend the assignments that are supposed to be active at a given state. pub fn extend_state_assignments(&mut self, state: u64, assigns: I) where I: IntoIterator>, diff --git a/calyx-opt/src/default_passes.rs b/calyx-opt/src/default_passes.rs index bad055eb6..41d9ceb33 100644 --- a/calyx-opt/src/default_passes.rs +++ b/calyx-opt/src/default_passes.rs @@ -1,6 +1,4 @@ //! Defines the default passes available to [PassManager]. -use petgraph::visit::Data; - use crate::pass_manager::PassResult; use crate::passes::{ AddGuard, Canonicalize, CellShare, ClkInsertion, CollapseControl, CombProp, @@ -134,6 +132,7 @@ impl PassManager { StaticInference, StaticPromotion, StaticFSMAllocation, + CompileRepeat, DeadGroupRemoval, MergeAssign, TopDownCompileControl, diff --git a/calyx-opt/src/passes/static_fsm_allocation.rs b/calyx-opt/src/passes/static_fsm_allocation.rs index e6abb3f56..c4b21cfef 100644 --- a/calyx-opt/src/passes/static_fsm_allocation.rs +++ b/calyx-opt/src/passes/static_fsm_allocation.rs @@ -1,8 +1,8 @@ use crate::traversal::{Action, ConstructVisitor, Named, Visitor}; use calyx_ir::{self as ir, build_assignments, guard}; use calyx_utils::CalyxResult; -use core::ops::Not; -use itertools::MultiUnzip; +use core::{num, ops::Not}; +use itertools::Itertools; use std::collections::HashMap; pub struct StaticFSMAllocation { @@ -29,9 +29,207 @@ impl ConstructVisitor for StaticFSMAllocation { } } +struct StaticSchedule<'a> { + /// Builder construct to add hardware to the component it's built from + builder: ir::Builder<'a>, + /// Maps every FSM state to assignments that should be active in that state + state2assigns: HashMap>>, + /// Maps every FSM state to transitions out of that state + state2trans: HashMap>, +} + +impl<'a> StaticSchedule<'a> { + /// Given a list of distinct previous states, a new state, and guards transitioning + /// to the new state, push ir::Transition objects to the `state2trans` map kept by + /// StaticSchedule + fn register_predecessors( + &mut self, + preds: &Vec<(u64, ir::Guard)>, + start_state: u64, + ) { + // add any transitions from previous parts of the schedule + preds.iter().for_each(|(pred_state, trans_guard)| { + let trans_to_current = if trans_guard.is_true() { + ir::Transition::Unconditional(start_state) + } else { + ir::Transition::Conditional(vec![( + trans_guard.clone(), + start_state, + )]) + }; + self.state2trans + .entry(*pred_state) + .and_modify(|other_trans| { + other_trans.push(trans_to_current.clone()) + }) + .or_insert(vec![trans_to_current]); + }); + } + + /// Given a static control schedule, mutably insert into the `state2assigns` + /// and `state2trans` fields of `self` to construct the schedule. Returns + /// the number of states added to the schedule after one function call. + fn construct_schedule_aux( + &mut self, + sc: &ir::StaticControl, + start_state: u64, + preds: Vec<(u64, ir::Guard)>, + guard_from_parent: ir::Guard, + ) -> (u64, Vec<(u64, ir::Guard)>) { + match sc { + ir::StaticControl::Empty(_) => (0, preds), + ir::StaticControl::Enable(sen) => { + // add any transitions from previous parts of the schedule + self.register_predecessors(&preds, start_state); + + // add assignments to be activated at states + sen.group.borrow().assignments.iter().for_each(|sassign| { + sassign + .guard + .compute_live_states(sen.group.borrow().latency) + .into_iter() + .for_each(|offset| { + // convert the static assignment to a normal one + let mut assign: ir::Assignment = + ir::Assignment::from(sassign.clone()); + // "and" the assignment's guard with argument guard + assign.and_guard(guard_from_parent.clone()); + let this_state = start_state + offset; + + // add this assignment to the list of assignments + // that are supposed to be valid at this state + self.state2assigns + .entry(this_state) + .and_modify(|other_assigns| { + other_assigns.push(assign.clone()) + }) + .or_insert(vec![assign]); + }) + }); + + // unconditionally transition to incremental states + let group_latency = sen.group.borrow().latency; + (0..(group_latency - 1)).for_each(|offset| { + let this_state = start_state + offset; + // unconditionally count up for static enables + self.state2trans + .entry(this_state) + .and_modify(|other_trans| { + other_trans.push(ir::Transition::Unconditional( + this_state + 1, + )) + }) + .or_insert(vec![ir::Transition::Unconditional( + this_state + 1, + )]); + }); + + ( + // here, the latency is exactly the number of states we added. + group_latency, + vec![(start_state + group_latency - 1, ir::Guard::True)], + ) + } + ir::StaticControl::Seq(sseq) => { + let (preds_from_seq, new_start_state) = sseq.stmts.iter().fold( + (preds, start_state), + |(stmt_preds, stmt_start_state), stmt| { + self.register_predecessors(&stmt_preds, start_state); + let (stmt_states_added, preds_from_stmt) = self + .construct_schedule_aux( + stmt, + stmt_start_state, + stmt_preds, + guard_from_parent.clone(), + ); + (preds_from_stmt, stmt_start_state + stmt_states_added) + }, + ); + + (new_start_state - start_state, preds_from_seq) + } + ir::StaticControl::If(sif) => { + self.register_predecessors(&preds, start_state); + + // construct a guard on the static assignments in the each branch + let build_branch_guard = + |is_true_branch: bool| -> ir::Guard { + guard_from_parent.clone().and({ + if is_true_branch { + ir::Guard::port(sif.port.clone()) + } else { + ir::Guard::not(ir::Guard::port( + sif.port.clone(), + )) + } + }) + }; + let (tbranch_num_states_added, trans_from_tbranch) = self + .construct_schedule_aux( + &sif.tbranch, + start_state, + preds.clone(), + build_branch_guard(true), + ); + let (fbranch_num_states_added, trans_from_fbranch) = self + .construct_schedule_aux( + &sif.fbranch, + start_state, + preds, + build_branch_guard(false), + ); + + // register transitions from the branches of the if + // to the node following the if block. the number of states + // added by the if block is the max of its individual branches + ( + u64::max( + tbranch_num_states_added, + fbranch_num_states_added, + ), + trans_from_tbranch + .into_iter() + .chain(trans_from_fbranch.into_iter()) + .sorted_by(|(s1, _), (s2, _)| s1.cmp(s2)) + .dedup() + .collect(), + ) + } + ir::StaticControl::Invoke(_) => { + unreachable!("`invoke` static control node should have been compiled away.") + } + ir::StaticControl::Par(_) => { + unreachable!( + "`par` control nodes should not exist in this schedule. \ + Only call this function on schedules where `par`s and `repeat`s \ + do not exist." + ) + } + ir::StaticControl::Repeat(_) => { + unreachable!( + "`repeat` control nodes should not exist in this schedule. + Only call this function on schedules where `par`s and `repeat`s \ + do not exist." + ) + } + } + } + + /// Entry point for an empty static schedule object. Returns the number of + /// states in the schedule, given an `ir::StaticControl` node. + fn construct_schedule(&mut self, sc: &ir::StaticControl) -> u64 { + // fill out schedule on static control node + let (num_states, loopback_transitions) = + self.construct_schedule_aux(sc, 0, vec![], ir::Guard::True); + // register transitions from final states to first state + self.register_predecessors(&loopback_transitions, 0); + num_states + } +} + /// An instance of `StaticSchedule` is constrainted to live at least as long as /// the component in which the static island that it represents lives. -struct StaticSchedule<'a> { +struct StaticSchedule2<'a> { /// Builder construct to add hardware to the component it's built from builder: ir::Builder<'a>, /// Number of cycles to which the static schedule should count up @@ -40,9 +238,9 @@ struct StaticSchedule<'a> { state2assigns: HashMap>>, } -impl<'a> From> for StaticSchedule<'a> { +impl<'a> From> for StaticSchedule2<'a> { fn from(builder: ir::Builder<'a>) -> Self { - StaticSchedule { + StaticSchedule2 { builder, latency: 0, state2assigns: HashMap::new(), @@ -50,7 +248,20 @@ impl<'a> From> for StaticSchedule<'a> { } } -impl<'a> StaticSchedule<'a> { +impl<'a> StaticSchedule2<'a> { + fn _print_state2assigns(&self) -> () { + for (state, assigns) in self.state2assigns.iter() { + println!("{state}:"); + for assign in assigns.iter() { + println!( + " dst: {}, src: {}", + assign.dst.borrow().canonical(), + assign.src.borrow().canonical() + ) + } + } + } + /// Provided a static control node, calling this method on an empty `StaticSchedule` /// `sch` will build out the `latency` and `state2assigns` fields of `sch`, in /// preparation to replace the `StaticControl` node with an instance of `ir::FSM`. @@ -60,7 +271,7 @@ impl<'a> StaticSchedule<'a> { fn construct_schedule( &mut self, scon: &ir::StaticControl, - guard_opt: Option>, + guard: ir::Guard, ) { match scon { ir::StaticControl::Empty(_) | ir::StaticControl::Invoke(_) => (), @@ -76,7 +287,7 @@ impl<'a> StaticSchedule<'a> { let mut assign: ir::Assignment = ir::Assignment::from(sassign.clone()); // "and" the assignment's guard with argument guard - assign.and_guard(guard_opt.clone()); + assign.and_guard(guard.clone()); // add this assignment to the list of assignments // that are supposed to be valid at this state self.state2assigns @@ -87,27 +298,24 @@ impl<'a> StaticSchedule<'a> { .or_insert(vec![assign]); }) }); + self.latency += sen.group.borrow().latency; } ir::StaticControl::Seq(sseq) => { sseq.stmts.iter().for_each(|stmt| { - self.construct_schedule(stmt, guard_opt.clone()); + self.construct_schedule(stmt, guard.clone()); }); } ir::StaticControl::Repeat(srep) => { for _ in 0..srep.num_repeats { - self.construct_schedule(&srep.body, guard_opt.clone()); + self.construct_schedule(&srep.body, guard.clone()); } } ir::StaticControl::If(sif) => { // construct a guard on the static assignments in the each branch let build_branch_guard = |is_true_branch: bool| -> ir::Guard { - (match guard_opt.clone() { - None => ir::Guard::True, - Some(existing_guard) => existing_guard, - }) - .and({ + guard.clone().and({ if is_true_branch { ir::Guard::port(sif.port.clone()) } else { @@ -121,14 +329,11 @@ impl<'a> StaticSchedule<'a> { // Since this construction will progress the schedule's latency, // we need to bring the baseline back to its original value before // doing the same for the false branch. - self.construct_schedule( - &sif.tbranch, - Some(build_branch_guard(true)), - ); + self.construct_schedule(&sif.tbranch, build_branch_guard(true)); self.latency -= sif.tbranch.get_latency(); self.construct_schedule( &sif.fbranch, - Some(build_branch_guard(false)), + build_branch_guard(false), ); self.latency -= sif.fbranch.get_latency(); // Finally, just progress the latency by the maximum of the @@ -139,7 +344,7 @@ impl<'a> StaticSchedule<'a> { // for each par thread, construct the schedule and reset // the baseline latency to correctly compile the next par thread spar.stmts.iter().for_each(|stmt| { - self.construct_schedule(stmt, guard_opt.clone()); + self.construct_schedule(stmt, guard.clone()); self.latency -= stmt.get_latency(); }); self.latency += spar.latency; @@ -162,10 +367,10 @@ impl<'a> StaticSchedule<'a> { // Fill in the FSM construct to contain unconditional transitions from n // to n+1 at each cycle (except for loopback at final state), and to // hold the corresponding state-wire high at the right cycle. - let (mut assignments, mut transitions, state2wires): ( + let mut state2wires: Vec> = vec![]; + let (mut assignments, mut transitions): ( Vec>>, Vec, - Vec>, ) = (0..self.latency) .map(|state: u64| { // construct a wire to represent this state @@ -184,7 +389,7 @@ impl<'a> StaticSchedule<'a> { let transition = if state == 0 { // merge first "calc" state with first "idle" state - state_assign.and_guard(Some(ir::guard!(fsm["start"]))); + state_assign.and_guard(ir::guard!(fsm["start"])); // set transition out of first state, which is conditional on reading fsm[start] ir::Transition::Conditional(vec![ @@ -201,10 +406,10 @@ impl<'a> StaticSchedule<'a> { }, ) }; - - (vec![state_assign], transition, state_wire) + state2wires.push(state_wire); + (vec![state_assign], transition) }) - .multiunzip(); + .unzip(); if non_promoted_static_component { // If the component is static by design, there will be exactly one @@ -228,7 +433,7 @@ impl<'a> StaticSchedule<'a> { self.builder .add_continuous_assignments(vec![assign_fsm_start]); } else if self.builder.component.is_static() { - // In this case, either the component is a promoted static component. + // In this case, the component is a promoted static component. // This means we want to wire fsm[done] in maintain the dynamic interface. // We will assert [done] in state 0. @@ -300,13 +505,13 @@ impl<'a> StaticSchedule<'a> { .drain() .flat_map(|(state, mut assigns)| { assigns.iter_mut().for_each(|assign| { - assign.and_guard(Some(ir::Guard::port( + assign.and_guard(ir::Guard::port( state2wires .get(state as usize) .unwrap() .borrow() .get("out"), - ))); + )); }); assigns }) @@ -332,8 +537,8 @@ impl Visitor for StaticFSMAllocation { && !(comp .attributes .has(ir::Attribute::Bool(ir::BoolAttr::Promoted))); - let mut ssch = StaticSchedule::from(ir::Builder::new(comp, sigs)); - ssch.construct_schedule(s, None); + let mut ssch = StaticSchedule2::from(ir::Builder::new(comp, sigs)); + ssch.construct_schedule(s, ir::Guard::True); Ok(Action::change(ir::Control::fsm_enable( ssch.realize_fsm(self.non_promoted_static_component), ))) diff --git a/runt.toml b/runt.toml index 3ec844613..1a43b28c4 100644 --- a/runt.toml +++ b/runt.toml @@ -258,7 +258,7 @@ cmd = """ fud exec --from calyx --to jq \ --through dat \ --through icarus-verilog \ - -s calyx.flags "-p fsm-opt2 -x tdcc:infer-fsms -p lower" \ + -s calyx.flags "-p fsm-opt -x tdcc:infer-fsms -p lower" \ -s verilog.cycle_limit 550 \ -s verilog.data {}.data \ -s jq.expr ".memories" \ @@ -280,7 +280,7 @@ cmd = """ fud exec --from calyx --to jq \ --through dat \ --through icarus-verilog \ - -s calyx.flags "-p fsm-opt2 -x tdcc:infer-fsms -p lower" \ + -s calyx.flags "-p fsm-opt -x tdcc:infer-fsms -p lower" \ -s verilog.cycle_limit 550 \ -s verilog.data {}.data \ -s jq.expr ".memories" \