Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compile static while #1431

Merged
merged 16 commits into from
May 15, 2023
156 changes: 153 additions & 3 deletions calyx-opt/src/passes/compile_static.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use super::math_utilities::get_bit_width_from;
use crate::traversal::{Action, Named, VisResult, Visitor};
use calyx_ir as ir;
use calyx_ir::{guard, structure, GetAttributes};
use ir::{build_assignments, Nothing, StaticTiming};
use calyx_utils::Error;
use ir::{build_assignments, Nothing, StaticTiming, RRC};
use itertools::Itertools;
use std::collections::HashMap;
use std::ops::Not;
Expand Down Expand Up @@ -239,6 +240,87 @@ impl CompileStatic {
early_reset_group.borrow().attributes.clone();
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());
paili0628 marked this conversation as resolved.
Show resolved Hide resolved
};

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",
paili0628 marked this conversation as resolved.
Show resolved Hide resolved
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.
/// We do this by wrapping the early reset group of the body with
/// another wrapper group, which sets the go signal of the early reset group
/// high, and is done when at the 0th cycle of each iteration, the condtion
/// port is done.
/// Note: this only works if the port for the while condition is `@stable`.
fn build_wrapper_group_while(
paili0628 marked this conversation as resolved.
Show resolved Hide resolved
&self,
fsm_name: &ir::Id,
fsm_width: u64,
group_name: &ir::Id,
port: RRC<ir::Port>,
builder: &mut ir::Builder,
) -> RRC<ir::Group> {
let reset_early_group = builder
.component
.find_group(*group_name)
.unwrap_or_else(|| {
unreachable!(
"called build_wrapper_group with {}, which is not a group",
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 wrapper_group =
builder.add_group(format!("while_wrapper_{}", group_name));

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]))
& guard!(early_reset_fsm["out"]).eq(guard!(time_0["out"]));

let assignments = build_assignments!(
builder;
// reset_early_group[go] = 1'd1;
// wrapper_group[done] = !port ? 1'd1;
reset_early_group["go"] = ? one["out"];
wrapper_group["done"] = done_guard ? one["out"];
);

wrapper_group.borrow_mut().assignments.extend(assignments);
wrapper_group
}
}

impl Visitor for CompileStatic {
Expand Down Expand Up @@ -306,7 +388,7 @@ impl Visitor for CompileStatic {
// 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());
return Err(Error::malformed_control(format!("Non-Enable Static Control should have been compiled away. Run {} to do this", crate::passes::StaticInliner::name())));
};

let sgroup = s.group.borrow_mut();
Expand All @@ -316,7 +398,7 @@ impl Visitor for CompileStatic {
let early_reset_name =
self.reset_early_map.get(&sgroup_name).unwrap_or_else(|| {
unreachable!(
"group {} not in self.reset_early_map",
"group {} early reset wrapper has not been created",
sgroup_name
)
});
Expand Down Expand Up @@ -346,6 +428,74 @@ impl Visitor for CompileStatic {
Ok(Action::Change(Box::new(e)))
}

/// 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
/// (all static control should be compiled into static groups by
/// `static_inliner` now). The done signal of the wrapper group should be
/// the condition that the fsm of the while body is %0 and the port signal
/// is 1'd0.
/// For example, we replace
/// ```
/// wires {
/// static group A<1> {
/// ...
/// }
/// ...
/// }

/// control {
/// while l.out {
/// A;
/// }
/// }
/// ```
/// with
/// ```
/// wires {
/// group early_reset_A {
/// ...
/// }
///
/// group while_wrapper_early_reset_A {
/// early_reset_A[go] = 1'd1;
/// while_wrapper_early_reset_A[done] = !l.out & fsm.out == 1'd0 ? 1'd1;
/// }
/// }
/// control {
/// while_wrapper_early_reset_A;
/// }
/// ```
fn start_while(
&mut self,
s: &mut ir::While,
comp: &mut ir::Component,
sigs: &ir::LibrarySignatures,
_comps: &[ir::Component],
) -> VisResult {
if s.cond.is_none() {
Copy link
Contributor

@calebmkim calebmkim May 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't have to go in this PR, but I was thinking that we could probably support comb groups in this case as well (I was also thinking for supporting static if comb groups). I may be missing something, but it seems like all we would have to do is trigger the comb group assignments at cycle 0 (in this case add an fsm.out == 0 guard to each of the comb group assignments, and then inline them into the wrapper group). The comb assignments will be "done instantaneously" so the port will be updated to reflect the comb group assignments at cycle 0, which is what we want?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be careful about with construct (see #927). Here is a particular case I ran into the last time I tried to handle if-with without creating extra groups out of it: #911 (comment)

The tried and true way to see if your reasoning about if-with lowering being correct is trying to make it work with the polybench integration tests.

if let ir::Control::Static(sc) = &mut *(s.body) {
let mut builder = ir::Builder::new(comp, sigs);
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 wrapper_group = self.build_wrapper_group_while(
fsm,
*fsm_width,
reset_group_name,
Rc::clone(&s.port),
&mut builder,
);
let c = ir::Control::enable(wrapper_group);
return Ok(Action::change(c));
}
}

Ok(Action::Continue)
}

fn finish(
&mut self,
comp: &mut ir::Component,
Expand Down
8 changes: 8 additions & 0 deletions tests/correctness/static-control/nested-static-while.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"cycles": 65,
"memories": {
"p": [
6
]
}
}
66 changes: 66 additions & 0 deletions tests/correctness/static-control/nested-static-while.futil
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import "primitives/core.futil";
import "primitives/pipelined.futil";

component main () -> () {
cells {
@external p = std_mem_d1(3,1,1);
incr = std_add(3);
l = std_lt(3);
r = std_reg(3);
l2 = std_lt(3);
r_cond = std_reg(1);
}

wires {
static<1> group A {
incr.left = p.read_data;
incr.right = 3'd1;
p.write_data = incr.out;
p.write_en = %0 ? 1'd1;
p.addr0 = 1'd0;
}

static<1> group A2 {
l.left = p.read_data;
l.right = 3'd6;
p.addr0 = 1'd0;
r_cond.in = l.out;
r_cond.write_en = 1'd1;
}


group B {
p.write_data = 3'd0;
p.write_en = 1'd1;
p.addr0 = 1'd0;
B[done] = p.done;
}

group C {
r.in = incr.out;
incr.left = r.out;
incr.right = 3'd1;
r.write_en = 1'd1;
C[done] = r.done;
}

comb group comp {
l2.left = r.out;
l2.right = 3'd3;
}

}

control {
while l2.out with comp {
seq {
B;
A2;
while r_cond.out {
static seq { A; A2; }
}
C;
}
}
}
}
12 changes: 12 additions & 0 deletions tests/correctness/static-control/nested-static-while.futil.data
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"p": {
"data": [
0
],
"format": {
"numeric_type": "bitnum",
"is_signed": false,
"width": 32
}
}
}
8 changes: 8 additions & 0 deletions tests/correctness/static-control/while.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"cycles": 21,
"memories": {
"p": [
6
]
}
}
45 changes: 45 additions & 0 deletions tests/correctness/static-control/while.futil
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import "primitives/core.futil";
import "primitives/pipelined.futil";

component main () -> () {
cells {
@external p = std_mem_d1(3,1,1);
incr = std_add(3);
l = std_lt(3);
r = std_reg(1);
s = std_reg(3);
}

wires {
static<1> group A {
incr.left = p.read_data;
incr.right = 3'd1;
s.in = incr.out;
s.write_en = %0 ? 1'd1;
p.addr0 = 1'd0;
}

static<1> group C {
p.write_data = s.out;
p.write_en = 1'd1;
p.addr0 = 1'd0;
}

static<1> group B {
l.left = p.read_data;
l.right = 3'd6;
r.in = l.out;
r.write_en = 1'd1;
p.addr0 = 1'd0;
}
}

control {
seq {
B;
while r.out {
static seq {A;C; B;}
}
}
}
}
12 changes: 12 additions & 0 deletions tests/correctness/static-control/while.futil.data
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"p": {
"data": [
0
],
"format": {
"numeric_type": "bitnum",
"is_signed": false,
"width": 32
}
}
}
Loading