From 58914de2bac127022b34996a3c11f73e27134c66 Mon Sep 17 00:00:00 2001 From: calebmkim <55243755+calebmkim@users.noreply.github.com> Date: Sun, 26 Nov 2023 22:08:53 -0500 Subject: [PATCH] Simplify logic on static designs (#1775) * command line cycle limit parse * clippy * better static guard simplification * rewrite test * small change * small change * rewrite test * default to none --- .../src/passes/simplify_static_guards.rs | 36 +++++- calyx-opt/src/passes/static_promotion.rs | 119 ++++++++++++------ examples/futil/dot-product.expect | 4 +- .../passes/simplify-static-guards/basic.futil | 16 +-- .../simplify-static-guards/simplify-or.expect | 21 ++++ .../simplify-static-guards/simplify-or.futil | 24 ++++ .../static-promotion/no_promote_loop.expect | 27 ++-- .../static-promotion/no_promote_loop.futil | 2 +- 8 files changed, 188 insertions(+), 61 deletions(-) create mode 100644 tests/passes/simplify-static-guards/simplify-or.expect create mode 100644 tests/passes/simplify-static-guards/simplify-or.futil diff --git a/calyx-opt/src/passes/simplify_static_guards.rs b/calyx-opt/src/passes/simplify_static_guards.rs index e22d3c2da0..3f8ac99b68 100644 --- a/calyx-opt/src/passes/simplify_static_guards.rs +++ b/calyx-opt/src/passes/simplify_static_guards.rs @@ -35,11 +35,6 @@ impl SimplifyStaticGuards { cur_anded_intervals: &mut Vec<(u64, u64)>, ) -> Option> { match g { - ir::Guard::Not(_) - | ir::Guard::Or(_, _) - | ir::Guard::True - | ir::Guard::CompOp(_, _, _) - | ir::Guard::Port(_) => Some(g), ir::Guard::And(g1, g2) => { // recursively call separate_anded_intervals on g1 and g2 let rest_g1 = @@ -65,6 +60,11 @@ impl SimplifyStaticGuards { cur_anded_intervals.push(static_timing_info.get_interval()); None } + ir::Guard::True + | ir::Guard::CompOp(_, _, _) + | ir::Guard::Not(_) + | ir::Guard::Or(_, _) + | ir::Guard::Port(_) => Some(g), } } @@ -75,7 +75,7 @@ impl SimplifyStaticGuards { /// For example: (port.out | !port1.out) & (port2.out == port3.out) & %[2:8] & %[5:10] ? /// becomes (port.out | !port1.out) & (port2.out == port3.out) & %[5:8] ? /// by "combining: %[2:8] & %[5:10] - fn simplify_guard( + fn simplify_anded_guards( guard: ir::Guard, group_latency: u64, ) -> ir::Guard { @@ -121,6 +121,30 @@ impl SimplifyStaticGuards { (Some(rg), Some(ig)) => ir::Guard::And(Box::new(rg), Box::new(ig)), } } + + fn simplify_guard( + guard: ir::Guard, + group_latency: u64, + ) -> ir::Guard { + match guard { + ir::Guard::Not(g) => ir::Guard::Not(Box::new( + Self::simplify_guard(*g, group_latency), + )), + ir::Guard::Or(g1, g2) => ir::Guard::Or( + Box::new(Self::simplify_guard(*g1, group_latency)), + Box::new(Self::simplify_guard(*g2, group_latency)), + ), + ir::Guard::And(_, _) => { + Self::simplify_anded_guards(guard, group_latency) + } + ir::Guard::Info(_) => { + Self::simplify_anded_guards(guard, group_latency) + } + ir::Guard::Port(_) + | ir::Guard::True + | ir::Guard::CompOp(_, _, _) => guard, + } + } } impl Visitor for SimplifyStaticGuards { diff --git a/calyx-opt/src/passes/static_promotion.rs b/calyx-opt/src/passes/static_promotion.rs index 7e95df3cf9..e5c3210d08 100644 --- a/calyx-opt/src/passes/static_promotion.rs +++ b/calyx-opt/src/passes/static_promotion.rs @@ -143,7 +143,7 @@ pub struct StaticPromotion { /// Threshold for promotion threshold: u64, /// Whether we should stop promoting when we see a loop. - stop_loop: bool, + cycle_limit: Option, } // Override constructor to build latency_data information from the primitives @@ -177,13 +177,13 @@ impl ConstructVisitor for StaticPromotion { } latency_data.insert(prim.name, GoDone::new(go_ports)); } - let (threshold, stop_loop) = Self::get_threshold(ctx); + let (threshold, cycle_limit) = Self::get_threshold(ctx); Ok(StaticPromotion { latency_data, static_group_name: HashMap::new(), static_component_latencies: HashMap::new(), threshold, - stop_loop, + cycle_limit, }) } @@ -206,7 +206,7 @@ impl Named for StaticPromotion { impl StaticPromotion { // Looks through ctx to get the given command line threshold. // Default threshold = 1 - fn get_threshold(ctx: &ir::Context) -> (u64, bool) + fn get_threshold(ctx: &ir::Context) -> (u64, Option) where Self: Named, { @@ -226,13 +226,24 @@ impl StaticPromotion { }) .collect(); - let mut stop_loop = false; - given_opts.iter().for_each(|arg| { - if *arg == "stop_loop" { - stop_loop = true + // searching for "-x static-promotion:cycle-limit=200" and getting back "200" + let cycle_limit_str: Option<&str> = given_opts.iter().find_map(|arg| { + let split: Vec<&str> = arg.split('=').collect(); + if let Some(str) = split.first() { + if str == &"cycle-limit" { + return Some(split[1]); + } } + None }); + // Default to None. There may be a more idiomatic way to do this. + let cycle_limit = match cycle_limit_str.unwrap_or("None").parse::() + { + Ok(n) => Some(n), + Err(_) => None, + }; + // searching for "-x static-promotion:threshold=1" and getting back "1" let threshold: Option<&str> = given_opts.iter().find_map(|arg| { let split: Vec<&str> = arg.split('=').collect(); @@ -246,9 +257,10 @@ impl StaticPromotion { // Need to convert string argument into int argument // Always default to threshold=1 + // Default cycle limit = 2^25 = 33554432 ( threshold.unwrap_or("1").parse::().unwrap_or(1), - stop_loop, + cycle_limit, ) } @@ -502,6 +514,13 @@ impl StaticPromotion { c.is_static() || c.has_attribute(ir::NumAttr::PromoteStatic) } + fn within_cycle_limit(&self, latency: u64) -> bool { + if self.cycle_limit.is_none() { + return true; + } + latency < self.cycle_limit.unwrap() + } + /// If we've already constructed the static group then use the already existing /// group. Otherwise construct `static group` and then return that. fn construct_static_group( @@ -743,18 +762,23 @@ impl StaticPromotion { v.iter().map(Self::approx_size).sum() } - /// First checks if the vec of control statements meets the self.threshold. + /// First checks if the vec of control statements satsifies the threshold + /// and cycle count threshold /// (That is, whether the combined approx_size of the static_vec is greater) - /// Than the threshold. + /// than the threshold and cycle count is less than cycle limit). /// If so, converts vec of control to a static seq, and returns a vec containing /// the static seq. /// Otherwise, just returns the vec without changing it. - fn convert_vec_seq_if_threshold( + fn convert_vec_seq_if_sat( &mut self, builder: &mut ir::Builder, control_vec: Vec, ) -> Vec { - if Self::approx_control_vec_size(&control_vec) <= self.threshold { + if Self::approx_control_vec_size(&control_vec) <= self.threshold + || !self.within_cycle_limit( + control_vec.iter().map(Self::get_inferred_latency).sum(), + ) + { // Return unchanged vec return control_vec; } @@ -768,16 +792,25 @@ impl StaticPromotion { vec![sseq] } - /// First checks if the vec of control statements meets the self.threshold. + /// First checks if the vec of control statements meets the self.threshold + /// and is within self.cycle_limit /// If so, converts vec of control to a static par, and returns a vec containing /// the static par. /// Otherwise, just returns the vec without changing it. - fn convert_vec_par_if_threshold( + fn convert_vec_par_if_sat( &mut self, builder: &mut ir::Builder, control_vec: Vec, ) -> Vec { - if Self::approx_control_vec_size(&control_vec) <= self.threshold { + if Self::approx_control_vec_size(&control_vec) <= self.threshold + || !self.within_cycle_limit( + control_vec + .iter() + .map(Self::get_inferred_latency) + .max() + .unwrap_or_else(|| unreachable!("Non Empty Par Block")), + ) + { // Return unchanged vec return control_vec; } @@ -933,17 +966,22 @@ impl Visitor for StaticPromotion { } else { // Accumualte cur_vec into a static seq if it meets threshold let possibly_promoted_stmts = - self.convert_vec_seq_if_threshold(&mut builder, cur_vec); + self.convert_vec_seq_if_sat(&mut builder, cur_vec); new_stmts.extend(possibly_promoted_stmts); - cur_vec = Vec::new(); // Add the current (non-promotable) stmt new_stmts.push(stmt); + // New cur_vec + cur_vec = Vec::new(); } } if new_stmts.is_empty() { // The entire seq can be promoted let approx_size: u64 = cur_vec.iter().map(Self::approx_size).sum(); - if approx_size > self.threshold { + if approx_size > self.threshold + && self.within_cycle_limit( + cur_vec.iter().map(Self::get_inferred_latency).sum(), + ) + { // Promote entire seq to a static seq let s_seq_stmts = self.convert_vec_to_static(&mut builder, cur_vec); @@ -970,7 +1008,7 @@ impl Visitor for StaticPromotion { // Entire seq is not static, so we're only promoting the last // bit of it if possible. let possibly_promoted_stmts = - self.convert_vec_seq_if_threshold(&mut builder, cur_vec); + self.convert_vec_seq_if_sat(&mut builder, cur_vec); new_stmts.extend(possibly_promoted_stmts); let new_seq = ir::Control::Seq(ir::Seq { @@ -997,7 +1035,15 @@ impl Visitor for StaticPromotion { }); if d_stmts.is_empty() { // Entire par block can be promoted to static - if Self::approx_control_vec_size(&s_stmts) > self.threshold { + if Self::approx_control_vec_size(&s_stmts) > self.threshold + && self.within_cycle_limit( + s_stmts + .iter() + .map(Self::get_inferred_latency) + .max() + .unwrap_or_else(|| unreachable!("Empty Par Block")), + ) + { // Promote entire par block to static let static_par_stmts = self.convert_vec_to_static(&mut builder, s_stmts); @@ -1025,7 +1071,7 @@ impl Visitor for StaticPromotion { } // Otherwise just promote the par threads that we can into a static par let s_stmts_possibly_promoted = - self.convert_vec_par_if_threshold(&mut builder, s_stmts); + self.convert_vec_par_if_sat(&mut builder, s_stmts); new_stmts.extend(s_stmts_possibly_promoted); new_stmts.extend(d_stmts); let new_par = ir::Control::Par(ir::Par { @@ -1050,16 +1096,18 @@ impl Visitor for StaticPromotion { let approx_size_if = Self::approx_size(&s.tbranch) + Self::approx_size(&s.fbranch) + APPROX_IF_SIZE; - if approx_size_if > self.threshold { + let latency = std::cmp::max( + Self::get_inferred_latency(&s.tbranch), + Self::get_inferred_latency(&s.fbranch), + ); + if approx_size_if > self.threshold + && self.within_cycle_limit(latency) + { // Meets size threshold so promote to static let static_tbranch = self.convert_to_static(&mut s.tbranch, &mut builder); let static_fbranch = self.convert_to_static(&mut s.fbranch, &mut builder); - let latency = std::cmp::max( - static_tbranch.get_latency(), - static_fbranch.get_latency(), - ); return Ok(Action::change(ir::Control::Static( ir::StaticControl::static_if( Rc::clone(&s.port), @@ -1090,9 +1138,6 @@ impl Visitor for StaticPromotion { sigs: &LibrarySignatures, _comps: &[ir::Component], ) -> VisResult { - if self.stop_loop { - return Ok(Action::Continue); - } let mut builder = ir::Builder::new(comp, sigs); // First check that while loop is bounded if let Some(num_repeats) = s.get_attributes().get(ir::NumAttr::Bound) { @@ -1100,11 +1145,13 @@ impl Visitor for StaticPromotion { if Self::can_be_promoted(&s.body) { let approx_size = Self::approx_size(&s.body) + APPROX_WHILE_REPEAT_SIZE; + let latency = Self::get_inferred_latency(&s.body) * num_repeats; // Then check that it reaches the threshold - if approx_size > self.threshold { + if approx_size > self.threshold + && self.within_cycle_limit(latency) + { // Turn repeat into static repeat let sc = self.convert_to_static(&mut s.body, &mut builder); - let latency = sc.get_latency() * num_repeats; let static_repeat = ir::StaticControl::repeat( num_repeats, latency, @@ -1134,18 +1181,16 @@ impl Visitor for StaticPromotion { sigs: &LibrarySignatures, _comps: &[ir::Component], ) -> VisResult { - if self.stop_loop { - return Ok(Action::Continue); - } let mut builder = ir::Builder::new(comp, sigs); if Self::can_be_promoted(&s.body) { // Body can be promoted let approx_size = Self::approx_size(&s.body) + APPROX_WHILE_REPEAT_SIZE; - if approx_size > self.threshold { + let latency = Self::get_inferred_latency(&s.body) * s.num_repeats; + if approx_size > self.threshold && self.within_cycle_limit(latency) + { // Meets size threshold, so turn repeat into static repeat let sc = self.convert_to_static(&mut s.body, &mut builder); - let latency = s.num_repeats * sc.get_latency(); let static_repeat = ir::StaticControl::repeat( s.num_repeats, latency, diff --git a/examples/futil/dot-product.expect b/examples/futil/dot-product.expect index c5f0d0db34..4021912c66 100644 --- a/examples/futil/dot-product.expect +++ b/examples/futil/dot-product.expect @@ -99,7 +99,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { B0.clk = clk; B0.addr0 = fsm.out == 4'd0 & early_reset_static_seq_go.out ? i0.out; B0.reset = reset; - B_read0_0.write_en = (fsm.out == 4'd0 & fsm.out < 4'd7 | fsm.out == 4'd5 & fsm.out < 4'd7) & early_reset_static_seq_go.out ? 1'd1; + B_read0_0.write_en = (fsm.out == 4'd0 | fsm.out == 4'd5) & early_reset_static_seq_go.out ? 1'd1; B_read0_0.clk = clk; B_read0_0.reset = reset; B_read0_0.in = fsm.out == 4'd0 & early_reset_static_seq_go.out ? B0.read_data; @@ -109,7 +109,7 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { early_reset_static_seq_done.in = ud0.out; tdcc_done.in = fsm0.out == 2'd3 ? 1'd1; while_wrapper_early_reset_static_seq_done.in = !comb_reg.out & fsm.out == 4'd0 ? 1'd1; - A_read0_0.write_en = (fsm.out == 4'd0 & fsm.out < 4'd7 | fsm.out == 4'd4 & fsm.out < 4'd7) & early_reset_static_seq_go.out ? 1'd1; + A_read0_0.write_en = (fsm.out == 4'd0 | fsm.out == 4'd4) & early_reset_static_seq_go.out ? 1'd1; A_read0_0.clk = clk; A_read0_0.reset = reset; A_read0_0.in = fsm.out == 4'd0 & early_reset_static_seq_go.out ? A0.read_data; diff --git a/tests/passes/simplify-static-guards/basic.futil b/tests/passes/simplify-static-guards/basic.futil index 4c3dbeec69..4351d57eb5 100644 --- a/tests/passes/simplify-static-guards/basic.futil +++ b/tests/passes/simplify-static-guards/basic.futil @@ -14,15 +14,15 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { } wires { static<10> group my_group { - a.write_en = (%[2:3] | lt.out) & %[1:5] ? 1'd1; // don't simplify - b.write_en = %[2:3] & (lt.out | gt.out) & %[1:5] ? 1'd1; // %[1:5] is redundant + a.write_en = (%[2:3] | lt.out) & %[1:5] ? 1'd1; // don't simplify + b.write_en = %[2:3] & (lt.out | gt.out) & %[1:5] ? 1'd1; // %[1:5] is redundant c.write_en = %[2:5] & (%[5:7] | lt.out) & %[3:7] & %[4:10] ? 1'd1; // %[5:7] shouldn't change, but can simplify rest to %[4:5] - d.write_en = %[2:5] & %[6:9] ? 1'd1; // assignment is false - e.write_en = %[0:10] & lt.out ? 1'd1; // no static timing necesary, since %[0:10] is same as group - a.in = 32'd1; - b.in = 32'd2; - c.in = 32'd3; - d.in = 32'd4; + d.write_en = %[2:5] & %[6:9] ? 1'd1; // assignment is false + e.write_en = %[0:10] & lt.out ? 1'd1; // no static timing necesary, since %[0:10] is same as group + a.in = 32'd1; + b.in = 32'd2; + c.in = 32'd3; + d.in = 32'd4; } } diff --git a/tests/passes/simplify-static-guards/simplify-or.expect b/tests/passes/simplify-static-guards/simplify-or.expect new file mode 100644 index 0000000000..b126ad06cd --- /dev/null +++ b/tests/passes/simplify-static-guards/simplify-or.expect @@ -0,0 +1,21 @@ +import "primitives/core.futil"; +import "primitives/binary_operators.futil"; +component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { + cells { + a = std_reg(32); + b = std_reg(32); + c = std_reg(32); + d = std_reg(32); + e = std_reg(32); + lt = std_lt(32); + gt = std_lt(32); + } + wires { + static<10> group my_group { + a.write_en = %2 | %5 | lt.out & %7 ? 1'd1; + } + } + control { + my_group; + } +} diff --git a/tests/passes/simplify-static-guards/simplify-or.futil b/tests/passes/simplify-static-guards/simplify-or.futil new file mode 100644 index 0000000000..a01944f16f --- /dev/null +++ b/tests/passes/simplify-static-guards/simplify-or.futil @@ -0,0 +1,24 @@ +// -p simplify-static-guards + +import "primitives/core.futil"; +import "primitives/binary_operators.futil"; +component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { + cells { + a = std_reg(32); + b = std_reg(32); + c = std_reg(32); + d = std_reg(32); + e = std_reg(32); + lt = std_lt(32); + gt = std_lt(32); + } + wires { + static<10> group my_group { + a.write_en = (%[2:3] & %[2:9]) | (%[5:9] & %[5:6]) | (%[7:9] & %[7:8] & lt.out) ? 1'd1; // don't simplify + } + } + + control { + my_group; + } +} diff --git a/tests/passes/static-promotion/no_promote_loop.expect b/tests/passes/static-promotion/no_promote_loop.expect index 26a22869ae..2aa38ac2b5 100644 --- a/tests/passes/static-promotion/no_promote_loop.expect +++ b/tests/passes/static-promotion/no_promote_loop.expect @@ -6,6 +6,21 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { c = std_reg(2); } wires { + group A<"promote_static"=1> { + a.in = 2'd0; + a.write_en = 1'd1; + A[done] = a.done; + } + group B<"promote_static"=1> { + b.in = 2'd1; + b.write_en = 1'd1; + B[done] = b.done; + } + group C<"promote_static"=1> { + c.in = 2'd2; + c.write_en = 1'd1; + C[done] = c.done; + } static<1> group A0 { a.in = 2'd0; a.write_en = 1'd1; @@ -20,8 +35,8 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { } } control { - seq { - repeat 10 { + @promote_static(43) seq { + @promote_static(40) repeat 10 { @compactable static<4> seq { A0; B0; @@ -29,11 +44,9 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) { C0; } } - @compactable static<3> seq { - A0; - B0; - C0; - } + @promote_static A; + @promote_static B; + @promote_static C; } } } diff --git a/tests/passes/static-promotion/no_promote_loop.futil b/tests/passes/static-promotion/no_promote_loop.futil index 36d9575ca9..7279175049 100644 --- a/tests/passes/static-promotion/no_promote_loop.futil +++ b/tests/passes/static-promotion/no_promote_loop.futil @@ -1,4 +1,4 @@ -// -p well-formed -p static-promotion -p dead-group-removal -x static-promotion:stop_loop +// -p well-formed -p static-promotion -p dead-group-removal -x static-promotion:cycle-limit=25 import "primitives/core.futil";