Skip to content

Commit

Permalink
Compaction Accounts for Continuous Assignments (#1847)
Browse files Browse the repository at this point in the history
* first pass

* code cleaning

* documentation

* clippy

* bug fix

* better analyssi

* documentation
  • Loading branch information
calebmkim authored Jan 12, 2024
1 parent 4f46a40 commit c945871
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 29 deletions.
91 changes: 69 additions & 22 deletions calyx-opt/src/analysis/control_order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -42,21 +42,19 @@ impl<const BETTER_ERR: bool> ControlOrder<BETTER_ERR> {
.unique()
}

fn get_cells_static_seq(
ports: Vec<RRC<ir::Port>>,
// Filters out the constants from `cells`, while mapping the remaining `ir:Cell`s
// to their cell name.
fn filter_out_constants(
cells: Vec<RRC<ir::Cell>>,
) -> impl Iterator<Item = ir::Id> {
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()
Expand Down Expand Up @@ -144,34 +142,55 @@ impl<const BETTER_ERR: bool> ControlOrder<BETTER_ERR> {
}
}

// 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<Item = ir::StaticControl>,
(cont_reads, cont_writes): (
Vec<ir::RRC<ir::Cell>>,
Vec<ir::RRC<ir::Cell>>,
),
dependency: &mut HashMap<NodeIndex, Vec<NodeIndex>>,
latency_map: &mut HashMap<NodeIndex, u64>,
) -> DiGraph<Option<ir::StaticControl>, ()> {
// 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<Option<ir::StaticControl>, ()> = DiGraph::new();

// Mapping name of cell to all the indices that read or write to it.
let mut reads: HashMap<ir::Id, Vec<NodeIndex>> = HashMap::default();
let mut writes: HashMap<ir::Id, Vec<NodeIndex>> = HashMap::default();

// Stores the nodes (i.e., control stmts) that are affected by continuous
// assignments
let mut continuous_idxs: HashSet<NodeIndex> = 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) {
Expand All @@ -180,10 +199,23 @@ impl<const BETTER_ERR: bool> ControlOrder<BETTER_ERR> {
}
}
}

// 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) {
Expand All @@ -193,6 +225,7 @@ impl<const BETTER_ERR: bool> ControlOrder<BETTER_ERR> {
}
}

// 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) {
Expand All @@ -202,6 +235,20 @@ impl<const BETTER_ERR: bool> ControlOrder<BETTER_ERR> {
}
}

// 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);
}
}
Expand Down
2 changes: 1 addition & 1 deletion calyx-opt/src/analysis/reaching_defns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ impl DefSet {
set: self
.set
.iter()
.filter(|&(name, _)| !killset.contains(name))
.cloned()
.filter(|(name, _)| !killset.contains(name))
.collect(),
}
}
Expand Down
65 changes: 63 additions & 2 deletions calyx-opt/src/analysis/read_write_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RRC<ir::Port>>, Vec<RRC<ir::Port>>) {
Expand Down Expand Up @@ -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<RRC<ir::Cell>>, Vec<RRC<ir::Cell>>) {
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<RRC<ir::Port>>, Vec<RRC<ir::Port>>) {
Expand Down Expand Up @@ -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<RRC<ir::Cell>>, Vec<RRC<ir::Cell>>) {
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<RRC<ir::Port>>, Vec<RRC<ir::Port>>) {
(
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<RRC<ir::Cell>>, Vec<RRC<ir::Cell>>) {
(
Self::read_set(comp.continuous_assignments.iter()).collect(),
Self::write_set(comp.continuous_assignments.iter()).collect(),
)
}
}
11 changes: 7 additions & 4 deletions calyx-opt/src/passes/schedule_compaction.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::analysis::ReadWriteSet;
use crate::traversal::Action;
use crate::{
analysis,
Expand All @@ -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 {
Expand Down Expand Up @@ -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<NodeIndex, u64> = 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::<false>::get_dependency_graph_static_seq(
s.stmts.drain(..),
(cont_reads, cont_writes),
&mut dependency,
&mut latency_map,
);
Expand All @@ -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
Expand Down
47 changes: 47 additions & 0 deletions tests/passes/schedule-compaction/continuous-compaction.expect
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
}
45 changes: 45 additions & 0 deletions tests/passes/schedule-compaction/continuous-compaction.futil
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
Loading

0 comments on commit c945871

Please sign in to comment.