Skip to content

Commit

Permalink
@fast attribute to enable early (0-cycle) transitions between alter…
Browse files Browse the repository at this point in the history
…nating dynamic/static groups (#2118)

Will eventually close #1828 once finalized and thoroughly tested (the
former of which may take a while).
  • Loading branch information
ethanuppal authored Aug 7, 2024
1 parent 8945254 commit 82da397
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 8 deletions.
4 changes: 4 additions & 0 deletions calyx-frontend/src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ pub enum BoolAttr {
#[strum(serialize = "promoted")]
/// denotes a static component or control promoted from dynamic
Promoted,
#[strum(serialize = "fast")]
/// https://github.com/calyxir/calyx/issues/1828
Fast,
}

impl From<BoolAttr> for Attribute {
fn from(attr: BoolAttr) -> Self {
Attribute::Bool(attr)
Expand Down
8 changes: 7 additions & 1 deletion calyx-opt/src/passes/static_promotion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::traversal::{
Action, ConstructVisitor, Named, Order, ParseVal, PassOpt, VisResult,
Visitor,
};
use calyx_ir::{self as ir, LibrarySignatures};
use calyx_ir::{self as ir, BoolAttr, LibrarySignatures};
use calyx_utils::CalyxResult;
use ir::GetAttributes;
use itertools::Itertools;
Expand Down Expand Up @@ -490,6 +490,12 @@ impl Visitor for StaticPromotion {
})
};
self.inference_analysis.fixup_ctrl(&mut new_ctrl);

// this might be part of a larger issue where passes remove some attributes they shouldn't
if s.get_attributes().has(BoolAttr::Fast) {
new_ctrl.get_mut_attributes().insert(BoolAttr::Fast, 1);
}

Ok(Action::change(new_ctrl))
}

Expand Down
18 changes: 14 additions & 4 deletions calyx-opt/src/passes/top_down_compile_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,8 @@ impl Schedule<'_, '_> {
preds: Vec<PredEdge>,
// True if early_transitions are allowed
early_transitions: bool,
// True if the `@fast` attribute has successfully been applied to the parent of this control
has_fast_guarantee: bool,
) -> CalyxResult<Vec<PredEdge>> {
match con {
// See explanation of FSM states generated in [ir::TopDownCompileControl].
Expand Down Expand Up @@ -746,7 +748,7 @@ impl Schedule<'_, '_> {
// NOTE: We explicilty do not add `not_done` to the guard.
// See explanation in [ir::TopDownCompileControl] to understand
// why.
if early_transitions {
if early_transitions || has_fast_guarantee {
for (st, g) in &prev_states {
let early_go = build_assignments!(self.builder;
group["go"] = g ? signal_on["out"];
Expand Down Expand Up @@ -793,9 +795,13 @@ impl Schedule<'_, '_> {
early_transitions: bool,
) -> CalyxResult<Vec<PredEdge>> {
let mut prev = preds;
for stmt in &seq.stmts {
prev =
self.calculate_states_recur(stmt, prev, early_transitions)?;
for (i, stmt) in seq.stmts.iter().enumerate() {
prev = self.calculate_states_recur(
stmt,
prev,
early_transitions,
i > 0 && seq.get_attributes().has(BoolAttr::Fast),
)?;
}
Ok(prev)
}
Expand Down Expand Up @@ -828,6 +834,7 @@ impl Schedule<'_, '_> {
&if_stmt.tbranch,
tru_transitions,
early_transitions,
false,
)?;
// Previous states transitioning into false branch need the conditional
// to be false.
Expand All @@ -845,6 +852,7 @@ impl Schedule<'_, '_> {
&if_stmt.fbranch,
fal_transitions,
early_transitions,
false,
)?
};

Expand Down Expand Up @@ -887,6 +895,7 @@ impl Schedule<'_, '_> {
&while_stmt.body,
transitions,
early_transitions,
false,
)?;

// Step 3: The final out edges from the while come from:
Expand Down Expand Up @@ -991,6 +1000,7 @@ impl Schedule<'_, '_> {
con,
vec![first_state],
early_transitions,
false,
)?;
self.add_nxt_transition(prev);
Ok(())
Expand Down
39 changes: 39 additions & 0 deletions calyx-opt/src/passes/well_formed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use calyx_ir::{
self as ir, Cell, CellType, Component, GetAttributes, LibrarySignatures,
RESERVED_NAMES,
};
use calyx_ir::{BoolAttr, Seq};
use calyx_utils::{CalyxResult, Error, WithPos};
use ir::Nothing;
use ir::StaticTiming;
Expand Down Expand Up @@ -282,6 +283,30 @@ fn subtype(cell_out: &Cell, cell_in: &Cell) -> bool {
true
}

/// Confirms (in agreement with [this discussion](https://github.com/calyxir/calyx/issues/1828))
/// that the `@fast` sequence `seq` is composed of alternating static-dynamic controls.
fn check_fast_seq_invariant(seq: &Seq) -> CalyxResult<()> {
if seq.stmts.is_empty() {
return Ok(());
}
let mut last_is_static = seq
.stmts
.first()
.expect("non-empty already asserted")
.is_static();
for stmt in seq.stmts.iter().skip(1) {
if stmt.is_static() == last_is_static {
return Err(
Error::malformed_control(
"`seq` marked `@fast` does not contain alternating static-dynamic control children (see #1828)"
)
);
}
last_is_static = stmt.is_static();
}
Ok(())
}

impl Visitor for WellFormed {
fn start(
&mut self,
Expand Down Expand Up @@ -741,6 +766,20 @@ impl Visitor for WellFormed {
Ok(Action::Continue)
}

fn start_seq(
&mut self,
s: &mut calyx_ir::Seq,
_comp: &mut Component,
_sigs: &LibrarySignatures,
_comps: &[calyx_ir::Component],
) -> VisResult {
if s.attributes.has(BoolAttr::Fast) {
check_fast_seq_invariant(s)?;
}

Ok(Action::Continue)
}

fn finish_if(
&mut self,
s: &mut ir::If,
Expand Down
30 changes: 27 additions & 3 deletions runt.toml
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,7 @@ timeout = 120

[[tests]]
name = "correctness static timing one-hot encoding"
paths = [
"tests/correctness/static-interface/*.futil",
]
paths = ["tests/correctness/static-interface/*.futil"]
cmd = """
fud exec --from calyx --to jq \
--through verilog \
Expand Down Expand Up @@ -444,6 +442,32 @@ fud e --from systolic --to jq \
"""
expect_dir = "tests/correctness/systolic/mmult-expect"

[[tests]]
name = "[correctness] `@fast` attribute should not change program behavior"
paths = ["tests/correctness/fast-attr/valid/*.futil"]
cmd = """
fud exec --from calyx --to jq \
--through dat \
--through icarus-verilog \
-s calyx.flags "-p validate -p compile -p lower -d static-promotion" \
-s verilog.data {}.data \
-s jq.expr "." \
{} -q
"""

[[tests]]
name = "[correctness] `@fast` attribute should reject unoptimizable sequences"
paths = ["tests/correctness/fast-attr/invalid/*.futil"]
cmd = """
fud exec --from calyx --to jq \
--through dat \
--through icarus-verilog \
-s calyx.flags "-p validate -p compile -p lower -d static-promotion" \
-s verilog.data {}.data \
-s jq.expr "." \
{} -q
"""

[[tests]]
name = "[frontend] systolic array relu static correctness"
paths = ["tests/correctness/systolic/relu-inputs/*.data"]
Expand Down
9 changes: 9 additions & 0 deletions tests/correctness/fast-attr/invalid/invalid.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---CODE---
255
---STDERR---
[fud] ERROR: `/home/calyx/target/debug/calyx -l /home/calyx -b verilog --disable-verify -p validate -p compile -p lower -d static-promotion' failed:
=====STDERR=====
Error: Malformed Control: `seq` marked `@fast` does not contain alternating static-dynamic control children (see #1828)

=====STDOUT=====

46 changes: 46 additions & 0 deletions tests/correctness/fast-attr/invalid/invalid.futil
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import "primitives/core.futil";
import "primitives/memories/comb.futil";

component main(@go go: 1) -> (@done done: 1) {
cells {
@external m = comb_mem_d1(32, 1, 32);
i0 = std_reg(32);
add = std_add(32);
}
wires {
static<1> group init {
i0.in = 32'd0;
i0.write_en = 1'b1;
}
group dyn_inc {
add.left = i0.out;
add.right = 32'd1;
i0.in = add.out;
i0.write_en = 1'b1;
dyn_inc[done] = i0.done;
}
static<1> group static_inc {
add.left = i0.out;
add.right = 32'd1;
i0.in = add.out;
i0.write_en = 1'b1;
}
group write {
m.write_data = i0.out;
m.write_en = 1'b1;
write[done] = m.done;
}
}
control {
@fast seq {
init;
dyn_inc;
dyn_inc;
dyn_inc;
static_inc;
static_inc;
static_inc;
write;
}
}
}
12 changes: 12 additions & 0 deletions tests/correctness/fast-attr/invalid/invalid.futil.data
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"m": {
"data": [
0
],
"format": {
"numeric_type": "bitnum",
"is_signed": false,
"width": 32
}
}
}
8 changes: 8 additions & 0 deletions tests/correctness/fast-attr/valid/fast6inc.expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"cycles": 9,
"memories": {
"m": [
6
]
}
}
46 changes: 46 additions & 0 deletions tests/correctness/fast-attr/valid/fast6inc.futil
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import "primitives/core.futil";
import "primitives/memories/comb.futil";

component main(@go go: 1) -> (@done done: 1) {
cells {
@external m = comb_mem_d1(32, 1, 32);
i0 = std_reg(32);
add = std_add(32);
}
wires {
static<1> group init {
i0.in = 32'd0;
i0.write_en = 1'b1;
}
group dyn_inc {
add.left = i0.out;
add.right = 32'd1;
i0.in = add.out;
i0.write_en = 1'b1;
dyn_inc[done] = i0.done;
}
static<1> group static_inc {
add.left = i0.out;
add.right = 32'd1;
i0.in = add.out;
i0.write_en = 1'b1;
}
group write {
m.write_data = i0.out;
m.write_en = 1'b1;
write[done] = m.done;
}
}
control {
@fast seq {
init;
dyn_inc;
static_inc;
dyn_inc;
static_inc;
dyn_inc;
static_inc;
write;
}
}
}
12 changes: 12 additions & 0 deletions tests/correctness/fast-attr/valid/fast6inc.futil.data
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"m": {
"data": [
0
],
"format": {
"numeric_type": "bitnum",
"is_signed": false,
"width": 32
}
}
}

0 comments on commit 82da397

Please sign in to comment.