Skip to content

Commit

Permalink
Single Port Memories (#1610)
Browse files Browse the repository at this point in the history
Implements a new `default-assigns` pass that adding missing assignments to non-`@data` ports.
andrewb1999 authored Feb 20, 2024
1 parent 41bb3fe commit fab34ab
Showing 52 changed files with 469 additions and 297 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -53,8 +53,8 @@ RUN python3 setup.py bdist_wheel && python3 -m pip install dist/tvm-*.whl
WORKDIR /home
RUN git clone https://github.com/cucapra/dahlia.git
WORKDIR /home/dahlia
## Checkout specific version
RUN git checkout 51954e7
## Checkout specific version. Fetch before checkout because clone might be cached.
RUN git fetch --all && git checkout 88e05e5
RUN sbt "; getHeaders; assembly"

# Add the Calyx source code from the build context
4 changes: 2 additions & 2 deletions calyx-ir/src/common.rs
Original file line number Diff line number Diff line change
@@ -32,9 +32,9 @@ impl<T: GetName> WRC<T> {
pub fn upgrade(&self) -> RRC<T> {
let Some(r) = self.internal.upgrade() else {
#[cfg(debug_assertions)]
unreachable!("weak reference points to a dropped. Original object's name: `{}'", self.debug_name);
unreachable!("weak reference points to a dropped value. Original object's name: `{}'", self.debug_name);
#[cfg(not(debug_assertions))]
unreachable!("weak reference points to a dropped.");
unreachable!("weak reference points to a dropped value.");
};
r
}
10 changes: 9 additions & 1 deletion calyx-ir/src/component.rs
Original file line number Diff line number Diff line change
@@ -231,6 +231,14 @@ impl Component {
self.namegen.gen_name(prefix)
}

/// Check whether this component is purely structural, i.e. has no groups or control
pub fn is_structural(&self) -> bool {
self.groups.is_empty()
&& self.comb_groups.is_empty()
&& self.static_groups.is_empty()
&& self.control.borrow().is_empty()
}

/// Check whether this is a static component.
/// A static component is a component which has a latency field.
pub fn is_static(&self) -> bool {
@@ -316,7 +324,7 @@ impl Component {
#[derive(Debug)]
pub struct IdList<T: GetName>(LinkedHashMap<Id, RRC<T>>);

/// Simple into-iter impl delegating to the [`Values`](linked_hash_map::Values).
/// Simple iter impl delegating to the [`Values`](linked_hash_map::Values).
impl<'a, T: GetName> IntoIterator for &'a IdList<T> {
type Item = &'a RRC<T>;

8 changes: 5 additions & 3 deletions calyx-opt/src/default_passes.rs
Original file line number Diff line number Diff line change
@@ -3,9 +3,9 @@ use crate::passes::{
AddGuard, Canonicalize, CellShare, ClkInsertion, CollapseControl, CombProp,
CompileInvoke, CompileRepeat, CompileStatic, CompileStaticInterface,
CompileSync, CompileSyncWithoutSyncReg, ComponentInliner, DataPathInfer,
DeadAssignmentRemoval, DeadCellRemoval, DeadGroupRemoval, DiscoverExternal,
Externalize, GoInsertion, GroupToInvoke, GroupToSeq, HoleInliner,
InferShare, LowerGuards, MergeAssign, Papercut, ParToSeq,
DeadAssignmentRemoval, DeadCellRemoval, DeadGroupRemoval, DefaultAssigns,
DiscoverExternal, Externalize, GoInsertion, GroupToInvoke, GroupToSeq,
HoleInliner, InferShare, LowerGuards, MergeAssign, Papercut, ParToSeq,
RegisterUnsharing, RemoveIds, ResetInsertion, SimplifyStaticGuards,
SimplifyWithControl, StaticInference, StaticInliner, StaticPromotion,
SynthesisPapercut, TopDownCompileControl, UnrollBounded, WellFormed,
@@ -59,6 +59,7 @@ impl PassManager {
pm.register_pass::<ResetInsertion>()?;
pm.register_pass::<MergeAssign>()?;
pm.register_pass::<WrapMain>()?;
pm.register_pass::<DefaultAssigns>()?;

// Enabled in the synthesis compilation flow
pm.register_pass::<SynthesisPapercut>()?;
@@ -134,6 +135,7 @@ impl PassManager {
ClkInsertion,
ResetInsertion,
MergeAssign,
DefaultAssigns,
]
);

129 changes: 129 additions & 0 deletions calyx-opt/src/passes/default_assigns.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use crate::analysis::AssignmentAnalysis;
use crate::traversal::{Action, ConstructVisitor, Named, VisResult, Visitor};
use calyx_ir::{self as ir, LibrarySignatures};
use calyx_utils::{CalyxResult, Error};
use itertools::Itertools;
use std::collections::HashMap;

/// Adds default assignments to all non-`@data` ports of an instance.
pub struct DefaultAssigns {
/// Mapping from component to data ports
data_ports: HashMap<ir::Id, Vec<ir::Id>>,
}

impl Named for DefaultAssigns {
fn name() -> &'static str {
"default-assigns"
}

fn description() -> &'static str {
"adds default assignments to all non-`@data` ports of an instance."
}
}

impl ConstructVisitor for DefaultAssigns {
fn from(ctx: &ir::Context) -> CalyxResult<Self>
where
Self: Sized,
{
let data_ports = ctx
.lib
.signatures()
.map(|sig| {
let ports = sig.signature.iter().filter_map(|p| {
if p.direction == ir::Direction::Input
&& !p.attributes.has(ir::BoolAttr::Data)
&& !p.attributes.has(ir::BoolAttr::Clk)
&& !p.attributes.has(ir::BoolAttr::Reset)
{
Some(p.name())
} else {
None
}
});
(sig.name, ports.collect())
})
.collect();
Ok(Self { data_ports })
}

fn clear_data(&mut self) {
/* shared across components */
}
}

impl Visitor for DefaultAssigns {
fn start(
&mut self,
comp: &mut ir::Component,
sigs: &LibrarySignatures,
_comps: &[ir::Component],
) -> VisResult {
if !comp.is_structural() {
return Err(Error::pass_assumption(
Self::name(),
format!("component {} is not purely structural", comp.name),
));
}

// We only need to consider write set of the continuous assignments
let writes = comp
.continuous_assignments
.iter()
.analysis()
.writes()
.group_by_cell();

let mut assigns = Vec::new();

let mt = vec![];
let cells = comp.cells.iter().cloned().collect_vec();
let mut builder = ir::Builder::new(comp, sigs);

for cr in &cells {
let cell = cr.borrow();
let Some(typ) = cell.type_name() else {
continue;
};
let Some(required) = self.data_ports.get(&typ) else {
continue;
};

// For all the assignments not in the write set, add a default assignment
let cell_writes = writes
.get(&cell.name())
.unwrap_or(&mt)
.iter()
.map(|p| {
let p = p.borrow();
p.name
})
.collect_vec();

assigns.extend(
required.iter().filter(|p| !cell_writes.contains(p)).map(
|name| {
let port = cell.get(name);
let zero = builder.add_constant(0, port.borrow().width);
let assign: ir::Assignment<ir::Nothing> = builder
.build_assignment(
cell.get(name),
zero.borrow().get("out"),
ir::Guard::True,
);
log::info!(
"Adding {}",
ir::Printer::assignment_to_str(&assign)
);
assign
},
),
);
}

comp.continuous_assignments.extend(assigns);

// Purely structural pass
Ok(Action::Stop)
}
}
2 changes: 2 additions & 0 deletions calyx-opt/src/passes/mod.rs
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@ mod sync;
mod add_guard;
mod compile_static_interface;
mod data_path_infer;
mod default_assigns;
mod discover_external;
mod simplify_with_control;
mod synthesis_papercut;
@@ -81,6 +82,7 @@ pub use sync::CompileSyncWithoutSyncReg;
// pub use simplify_guards::SimplifyGuards;
pub use add_guard::AddGuard;
pub use compile_static_interface::CompileStaticInterface;
pub use default_assigns::DefaultAssigns;
pub use synthesis_papercut::SynthesisPapercut;
pub use top_down_compile_control::TopDownCompileControl;
pub use unroll_bound::UnrollBounded;
65 changes: 47 additions & 18 deletions calyx-opt/src/passes/papercut.rs
Original file line number Diff line number Diff line change
@@ -28,6 +28,30 @@ pub struct Papercut {
cont_cells: HashSet<ir::Id>,
}

impl Papercut {
#[allow(unused)]
/// String representation of the write together and read together specifications.
/// Used for debugging. Should not be relied upon by external users.
fn fmt_write_together_spec(&self) -> String {
self.write_together
.iter()
.map(|(prim, writes)| {
let writes = writes
.iter()
.map(|write| {
write
.iter()
.sorted()
.map(|port| format!("{port}"))
.join(", ")
})
.join("; ");
format!("{}: [{}]", prim, writes)
})
.join("\n")
}
}

impl ConstructVisitor for Papercut {
fn from(ctx: &ir::Context) -> CalyxResult<Self> {
let write_together =
@@ -232,7 +256,7 @@ impl Papercut {
.join(", ");
let msg =
format!("Required signal not driven inside the group.\
\nWhen read the port `{}.{}', the ports [{}] must be written to.\
\nWhen reading the port `{}.{}', the ports [{}] must be written to.\
\nThe primitive type `{}' requires this invariant.",
inst,
read,
@@ -245,30 +269,35 @@ impl Papercut {
}
for ((inst, comp_type), writes) in all_writes {
if let Some(spec) = self.write_together.get(&comp_type) {
// For each write together spec.
for required in spec {
// It should either be the case that:
// 1. `writes` contains no writes that overlap with `required`
// In which case `required - writes` == `required`.
// 2. `writes` contains writes that overlap with `required`
// In which case `required - writes == {}`
let mut diff = required - &writes;
if !diff.is_empty() && diff != *required {
let first = writes.iter().sorted().next().unwrap();
let missing = diff
.drain()
.sorted()
.map(|port| format!("{}.{}", inst, port))
.join(", ");
let msg =
format!("Required signal not driven inside the group.\
\nWhen writing to the port `{}.{}', the ports [{}] must also be written to.\
\nThe primitive type `{}' requires this invariant.",
inst,
first,
missing,
comp_type);
return Err(Error::papercut(msg));
let mut diff: HashSet<_> =
required.difference(&writes).copied().collect();
if diff.is_empty() || diff == *required {
continue;
}

let first =
writes.intersection(required).sorted().next().unwrap();
let missing = diff
.drain()
.sorted()
.map(|port| format!("{}.{}", inst, port))
.join(", ");
let msg =
format!("Required signal not driven inside the group. \
When writing to the port `{}.{}', the ports [{}] must also be written to. \
The primitive type `{}' specifies this using a @write_together spec.",
inst,
first,
missing,
comp_type);
return Err(Error::papercut(msg));
}
}
}
9 changes: 5 additions & 4 deletions calyx-py/calyx/builder.py
Original file line number Diff line number Diff line change
@@ -605,8 +605,8 @@ def mem_read_seq_d1(self, mem, i, groupname=None):
groupname = groupname or f"read_from_{mem.name()}"
with self.group(groupname) as read_grp:
mem.addr0 = i
mem.read_en = 1
read_grp.done = mem.read_done
mem.content_en = 1
read_grp.done = mem.done
return read_grp

def mem_write_seq_d1_to_reg(self, mem, reg, groupname=None):
@@ -631,7 +631,8 @@ def mem_store_seq_d1(self, mem, i, val, groupname=None):
mem.addr0 = i
mem.write_en = 1
mem.write_data = val
store_grp.done = mem.write_done
mem.content_en = 1
store_grp.done = mem.done
return store_grp

def mem_load_to_mem(self, mem, i, ans, j, groupname=None):
@@ -1099,7 +1100,7 @@ def infer_width(self, port_name) -> int:
return inst.args[2]
if port_name == "in":
return inst.args[0]
if prim == "seq_mem_d1" and port_name == "read_en":
if prim == "seq_mem_d1" and port_name == "content_en":
return 1
if prim in (
"std_mult_pipe",
2 changes: 2 additions & 0 deletions examples/futil/dot-product.expect
Original file line number Diff line number Diff line change
@@ -115,6 +115,8 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) {
A_read0_0.reset = reset;
A_read0_0.in = fsm.out == 4'd0 & early_reset_static_seq_go.out ? A0.read_data;
A_read0_0.in = fsm.out == 4'd4 & early_reset_static_seq_go.out ? mult_pipe0.out;
A0.write_en = 1'd0;
B0.write_en = 1'd0;
}
control {}
}
2 changes: 2 additions & 0 deletions examples/futil/vectorized-add.expect
Original file line number Diff line number Diff line change
@@ -105,6 +105,8 @@ component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (@done done: 1) {
A_read0_0.clk = clk;
A_read0_0.reset = reset;
A_read0_0.in = fsm.out == 3'd0 & early_reset_static_seq_go.out ? A0.read_data;
A0.write_en = 1'd0;
B0.write_en = 1'd0;
}
control {}
}
Loading

0 comments on commit fab34ab

Please sign in to comment.