From c94587138a382895925e2b34d04a17fb1c4705f6 Mon Sep 17 00:00:00 2001 From: calebmkim <55243755+calebmkim@users.noreply.github.com> Date: Fri, 12 Jan 2024 16:46:19 -0500 Subject: [PATCH] Compaction Accounts for Continuous Assignments (#1847) * first pass * code cleaning * documentation * clippy * bug fix * better analyssi * documentation --- calyx-opt/src/analysis/control_order.rs | 91 ++++++++++++++----- calyx-opt/src/analysis/reaching_defns.rs | 2 +- calyx-opt/src/analysis/read_write_set.rs | 65 ++++++++++++- calyx-opt/src/passes/schedule_compaction.rs | 11 ++- .../continuous-compaction.expect | 47 ++++++++++ .../continuous-compaction.futil | 45 +++++++++ .../continuous-no-compaction.expect | 40 ++++++++ .../continuous-no-compaction.futil | 46 ++++++++++ 8 files changed, 318 insertions(+), 29 deletions(-) create mode 100644 tests/passes/schedule-compaction/continuous-compaction.expect create mode 100644 tests/passes/schedule-compaction/continuous-compaction.futil create mode 100644 tests/passes/schedule-compaction/continuous-no-compaction.expect create mode 100644 tests/passes/schedule-compaction/continuous-no-compaction.futil diff --git a/calyx-opt/src/analysis/control_order.rs b/calyx-opt/src/analysis/control_order.rs index 27102ebfe1..5edb9a290c 100644 --- a/calyx-opt/src/analysis/control_order.rs +++ b/calyx-opt/src/analysis/control_order.rs @@ -7,7 +7,7 @@ use petgraph::{ algo, graph::{DiGraph, NodeIndex}, }; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt::Write as _; /// Extract the dependency order of a list of control programs. @@ -42,21 +42,19 @@ impl ControlOrder { .unique() } - fn get_cells_static_seq( - ports: Vec>, + // Filters out the constants from `cells`, while mapping the remaining `ir:Cell`s + // to their cell name. + fn filter_out_constants( + cells: Vec>, ) -> impl Iterator { - ports + cells .into_iter() - .filter_map(|p| { - let cr = p.borrow().cell_parent(); - let cell = cr.borrow(); - match cell.prototype { - // Ignore constants and _this - ir::CellType::Constant { .. } => None, - ir::CellType::ThisComponent => { - Some(ir::Id::new("this_comp")) - } - _ => Some(cell.name()), + .filter_map(|cell| match cell.borrow().prototype { + ir::CellType::Constant { .. } => None, + ir::CellType::Component { .. } + | ir::CellType::Primitive { .. } + | ir::CellType::ThisComponent { .. } => { + Some(cell.borrow().name()) } }) .unique() @@ -144,16 +142,32 @@ impl ControlOrder { } } - // returns a graph of dependency for input programs - // input control programs are considered to have data dependency if: + // Returns a graph of dependency for input programs. + // Input control programs are considered to have data dependency if: // 1. subsequent program writes to cells that previous program reads from // 2. subsequent program writes to cells that previous program writes to // 3. subsequent program reads from cells that previous program writes to + // Furthermore, we add dependencies due to continuous assignments as well. If: + // 4. Program writes to cell that a continuous assignment writes to or reads from. + // 5. Program reads from a cell that a continuous assignment writes to. + // Then the program "touches" the continuous assignments, and therefore depends + // on all previous programs that "touched" continuous assignments as well. + // In short, we treat continuous assignments as one big cell. pub fn get_dependency_graph_static_seq( stmts: impl Iterator, + (cont_reads, cont_writes): ( + Vec>, + Vec>, + ), dependency: &mut HashMap>, latency_map: &mut HashMap, ) -> DiGraph, ()> { + // The names of the cells that are read/written in continuous assignments + let cont_read_cell_names = + Self::filter_out_constants(cont_reads).collect_vec(); + let cont_write_cell_names = + Self::filter_out_constants(cont_writes).collect_vec(); + // Directed graph where edges means that a control program must be run before. let mut gr: DiGraph, ()> = DiGraph::new(); @@ -161,17 +175,22 @@ impl ControlOrder { let mut reads: HashMap> = HashMap::default(); let mut writes: HashMap> = HashMap::default(); + // Stores the nodes (i.e., control stmts) that are affected by continuous + // assignments + let mut continuous_idxs: HashSet = HashSet::new(); + for c in stmts { - let (port_reads, port_writes) = - ReadWriteSet::control_port_read_write_set_static(&c); - let r_cells = Self::get_cells_static_seq(port_reads); - let w_cells = Self::get_cells_static_seq(port_writes); + let (cell_reads, cell_writes) = + ReadWriteSet::control_read_write_set_static(&c); + let r_cell_names = Self::filter_out_constants(cell_reads); + let w_cell_names = Self::filter_out_constants(cell_writes); let latency = c.get_latency(); let idx = gr.add_node(Some(c)); dependency.insert(idx, Vec::new()); latency_map.insert(idx, latency); - for cell in r_cells { + for cell in r_cell_names { + // Checking: 3. subsequent program reads from cells that previous program writes to if let Some(wr_idxs) = writes.get(&cell) { for wr_idx in wr_idxs { if !wr_idx.eq(&idx) { @@ -180,10 +199,23 @@ impl ControlOrder { } } } + + // Checking: 5. Program reads from a cell that a continuous + // assignment writes to. + if cont_write_cell_names.contains(&cell) { + for cur_idx in continuous_idxs.iter() { + if !cur_idx.eq(&idx) { + gr.add_edge(*cur_idx, idx, ()); + dependency.entry(idx).or_default().push(*cur_idx); + } + } + continuous_idxs.insert(idx); + } reads.entry(cell).or_default().push(idx); } - for cell in w_cells { + for cell in w_cell_names { + // Checking: 2. subsequent program writes to cells that previous program writes to if let Some(wr_idxs) = writes.get(&cell) { for wr_idx in wr_idxs { if !wr_idx.eq(&idx) { @@ -193,6 +225,7 @@ impl ControlOrder { } } + // Checking: 1. subsequent program writes to cells that previous program reads from if let Some(r_idxs) = reads.get(&cell) { for r_idx in r_idxs { if !r_idx.eq(&idx) { @@ -202,6 +235,20 @@ impl ControlOrder { } } + // Checking: 4. Program writes to cell that a continuous assignment + // writes to or reads from. + if cont_write_cell_names.contains(&cell) + || cont_read_cell_names.contains(&cell) + { + for cur_idx in continuous_idxs.iter() { + if !cur_idx.eq(&idx) { + gr.add_edge(*cur_idx, idx, ()); + dependency.entry(idx).or_default().push(*cur_idx); + } + } + continuous_idxs.insert(idx); + } + writes.entry(cell).or_default().push(idx); } } diff --git a/calyx-opt/src/analysis/reaching_defns.rs b/calyx-opt/src/analysis/reaching_defns.rs index 0e2b908747..384cfda858 100644 --- a/calyx-opt/src/analysis/reaching_defns.rs +++ b/calyx-opt/src/analysis/reaching_defns.rs @@ -143,8 +143,8 @@ impl DefSet { set: self .set .iter() + .filter(|&(name, _)| !killset.contains(name)) .cloned() - .filter(|(name, _)| !killset.contains(name)) .collect(), } } diff --git a/calyx-opt/src/analysis/read_write_set.rs b/calyx-opt/src/analysis/read_write_set.rs index de1103e7b8..544a17d886 100644 --- a/calyx-opt/src/analysis/read_write_set.rs +++ b/calyx-opt/src/analysis/read_write_set.rs @@ -120,7 +120,8 @@ impl ReadWriteSet { } impl ReadWriteSet { - /// Returns the ports that are read by the given control program. + /// Returns the ports that are read and written, respectively, + /// by the given static control program. pub fn control_port_read_write_set_static( scon: &ir::StaticControl, ) -> (Vec>, Vec>) { @@ -193,7 +194,27 @@ impl ReadWriteSet { } } - /// Returns the ports that are read by the given control program. + /// Returns the cells that are read and written, respectively, + /// by the given static control program. + pub fn control_read_write_set_static( + scon: &ir::StaticControl, + ) -> (Vec>, Vec>) { + let (port_reads, port_writes) = + Self::control_port_read_write_set_static(scon); + ( + port_reads + .into_iter() + .map(|p| p.borrow().cell_parent()) + .collect(), + port_writes + .into_iter() + .map(|p| p.borrow().cell_parent()) + .collect(), + ) + } + + /// Returns the ports that are read and written, respectively, + /// by the given control program. pub fn control_port_read_write_set( con: &ir::Control, ) -> (Vec>, Vec>) { @@ -305,4 +326,44 @@ impl ReadWriteSet { } } } + + /// Returns the cells that are read and written, respectively, + /// by the given control program. + pub fn control_read_write_set( + con: &ir::Control, + ) -> (Vec>, Vec>) { + let (port_reads, port_writes) = Self::control_port_read_write_set(con); + ( + port_reads + .into_iter() + .map(|p| p.borrow().cell_parent()) + .collect(), + port_writes + .into_iter() + .map(|p| p.borrow().cell_parent()) + .collect(), + ) + } + + /// Returns the ports that are read and written, respectively, + /// in the continuous assignments of the given component. + pub fn cont_ports_read_write_set( + comp: &mut calyx_ir::Component, + ) -> (Vec>, Vec>) { + ( + Self::port_read_set(comp.continuous_assignments.iter()).collect(), + Self::port_write_set(comp.continuous_assignments.iter()).collect(), + ) + } + + /// Returns the cells that are read and written, respectively, + /// in the continuous assignments of the given component. + pub fn cont_read_write_set( + comp: &mut calyx_ir::Component, + ) -> (Vec>, Vec>) { + ( + Self::read_set(comp.continuous_assignments.iter()).collect(), + Self::write_set(comp.continuous_assignments.iter()).collect(), + ) + } } diff --git a/calyx-opt/src/passes/schedule_compaction.rs b/calyx-opt/src/passes/schedule_compaction.rs index c18bca7630..ec440da8ec 100644 --- a/calyx-opt/src/passes/schedule_compaction.rs +++ b/calyx-opt/src/passes/schedule_compaction.rs @@ -1,3 +1,4 @@ +use crate::analysis::ReadWriteSet; use crate::traversal::Action; use crate::{ analysis, @@ -8,10 +9,10 @@ use petgraph::{algo, graph::NodeIndex}; use std::collections::HashMap; #[derive(Default)] -/// for static seqs that are statically promoted by the compiler, -/// aggressively compacts the execution schedule so that the execution +/// For static seqs that are statically promoted by the compiler. +/// Aggressively compacts the execution schedule so that the execution /// order of control operators still respects data dependency -/// Example: see tests/passes/schedule-compaction/schedule-compaction.rs +/// Example: see tests/passes/schedule-compaction/schedule-compaction.futil pub struct ScheduleCompaction; impl Named for ScheduleCompaction { @@ -47,11 +48,14 @@ impl Visitor for ScheduleCompaction { // records the scheduled start time of corresponding control operator for each node index let mut schedule: HashMap = HashMap::new(); + let (cont_reads, cont_writes) = ReadWriteSet::cont_read_write_set(comp); + let mut builder = ir::Builder::new(comp, sigs); let mut total_order = analysis::ControlOrder::::get_dependency_graph_static_seq( s.stmts.drain(..), + (cont_reads, cont_writes), &mut dependency, &mut latency_map, ); @@ -60,7 +64,6 @@ impl Visitor for ScheduleCompaction { let mut total_time: u64 = 0; // First we build the schedule. - for i in order { // Start time is when the latest dependency finishes let start = dependency diff --git a/tests/passes/schedule-compaction/continuous-compaction.expect b/tests/passes/schedule-compaction/continuous-compaction.expect new file mode 100644 index 0000000000..1ccd2ef0f5 --- /dev/null +++ b/tests/passes/schedule-compaction/continuous-compaction.expect @@ -0,0 +1,47 @@ +import "primitives/core.futil"; +component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (out: 8, @done done: 1) { + cells { + r0 = std_reg(8); + r1 = std_reg(8); + r2 = std_reg(8); + r3 = std_reg(8); + add = std_add(8); + } + wires { + static<1> group write_r0 { + r0.in = 8'd1; + r0.write_en = 1'd1; + } + static<1> group write_r1 { + r1.in = add.out; + r1.write_en = 1'd1; + } + static<1> group write_r2 { + r2.in = 8'd3; + r2.write_en = 1'd1; + } + static<1> group write_r3 { + r3.in = 8'd3; + r3.write_en = 1'd1; + } + out = r1.out; + add.right = 8'd1; + add.left = r0.out; + } + control { + seq { + static<2> par { + static<2> seq { + write_r0; + write_r1; + } + static<1> seq { + write_r2; + } + static<1> seq { + write_r3; + } + } + } + } +} diff --git a/tests/passes/schedule-compaction/continuous-compaction.futil b/tests/passes/schedule-compaction/continuous-compaction.futil new file mode 100644 index 0000000000..017c13cda3 --- /dev/null +++ b/tests/passes/schedule-compaction/continuous-compaction.futil @@ -0,0 +1,45 @@ +// -p validate -p schedule-compaction + +import "primitives/core.futil"; + +component main() -> (out: 8) { + cells { + r0 = std_reg(8); + r1 = std_reg(8); + r2 = std_reg(8); + r3 = std_reg(8); + add = std_add(8); + } + wires { + static<1> group write_r0 { + r0.write_en = 1'd1; + r0.in = 8'd1; + } + static<1> group write_r1 { + r1.write_en = 1'd1; + r1.in = add.out; + } + static<1> group write_r2 { + r2.write_en = 1'd1; + r2.in = 8'd3; + } + static<1> group write_r3 { + r3.write_en = 1'd1; + r3.in = 8'd3; + } + add.left = r0.out; + add.right = 8'd1; + out = r1.out; + } + control { + seq { + @compactable static seq { + write_r0; + // Continuous assignments to add.left and add.right prevent compation. + write_r1; + write_r2; + write_r3; + } + } + } +} \ No newline at end of file diff --git a/tests/passes/schedule-compaction/continuous-no-compaction.expect b/tests/passes/schedule-compaction/continuous-no-compaction.expect new file mode 100644 index 0000000000..b61eaf801e --- /dev/null +++ b/tests/passes/schedule-compaction/continuous-no-compaction.expect @@ -0,0 +1,40 @@ +import "primitives/core.futil"; +component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (out: 8, @done done: 1) { + cells { + r0 = std_reg(8); + r1 = std_reg(8); + r2 = std_reg(8); + add = std_add(8); + add1 = std_add(8); + } + wires { + static<1> group write_r0 { + r0.in = 8'd1; + r0.write_en = 1'd1; + } + static<1> group write_r1 { + r1.in = add.out; + r1.write_en = 1'd1; + } + static<1> group write_add1 { + add1.right = 8'd4; + add1.left = 8'd1; + } + out = r1.out; + add.right = 8'd1; + add.left = r0.out; + r2.in = add1.out; + } + control { + seq { + static<2> seq { + write_r0; + write_r1; + } + static<2> seq { + write_r0; + write_add1; + } + } + } +} diff --git a/tests/passes/schedule-compaction/continuous-no-compaction.futil b/tests/passes/schedule-compaction/continuous-no-compaction.futil new file mode 100644 index 0000000000..c69eb1a3f4 --- /dev/null +++ b/tests/passes/schedule-compaction/continuous-no-compaction.futil @@ -0,0 +1,46 @@ +// -p validate -p schedule-compaction + +import "primitives/core.futil"; + +component main() -> (out: 8) { + cells { + r0 = std_reg(8); + r1 = std_reg(8); + r2 = std_reg(8); + add = std_add(8); + add1 = std_add(8); + } + wires { + static<1> group write_r0 { + r0.write_en = 1'd1; + r0.in = 8'd1; + } + static<1> group write_r1 { + r1.write_en = 1'd1; + r1.in = add.out; + } + static<1> group write_add1 { + add1.left = 8'd1; + add1.right = 8'd4; + } + r2.in = add1.out; + add.left = r0.out; + add.right = 8'd1; + out = r1.out; + } + control { + seq { + @compactable static seq { + write_r0; + // Continuous assignments to add.left and add.right prevent compation. + write_r1; + } + @compactable static seq { + write_r0; + // Continuous assignment r2.in = add1.out prevents compaction. + // This is overly conservative. + write_add1; + } + } + } +} \ No newline at end of file