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

[Calyx] Implement combinational groups #1736

Merged
merged 7 commits into from
Sep 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions include/circt/Dialect/Calyx/CalyxControl.td
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ def IfOp : CalyxContainer<"if", [
let summary = "Calyx If";
let arguments = (ins
I1:$cond,
FlatSymbolRefAttr:$groupName
OptionalAttr<FlatSymbolRefAttr>:$groupName
);
let regions = (region SizedRegion<1>:$thenRegion, AnyRegion:$elseRegion);
let description = [{
The "calyx.if" operation represents and if-then-else construct for
conditionally executing two Calyx groups. The operands to an if operation is
a 1-bit port and the group under which this port is driven.
a 1-bit port and an optional combinational group under which this port is driven.

Note: The native and CIRCT Calyx IRs may diverge wrt. 'with' execution, see:
https://github.com/cucapra/calyx/discussions/588
Expand All @@ -59,10 +59,14 @@ def IfOp : CalyxContainer<"if", [
calyx.enable @G3
...
}
calyx.if %1 {
calyx.enable @G2
...
}
```
}];

let assemblyFormat = "$cond `with` $groupName $thenRegion (`else` $elseRegion^)? attr-dict";
let assemblyFormat = "$cond (`with` $groupName^)? $thenRegion (`else` $elseRegion^)? attr-dict";
let verifier = "return ::verify$cppClass(*this);";
}

Expand Down Expand Up @@ -134,13 +138,13 @@ def WhileOp : CalyxContainer<"while", [
let summary = "Calyx While";
let arguments = (ins
I1:$cond,
FlatSymbolRefAttr:$groupName
OptionalAttr<FlatSymbolRefAttr>:$groupName
);
let description = [{
The "calyx.while" operation represents a construct for continuously
executing the inner groups of the 'while' operation while the condition port
evaluates to true. The operands to a while operation is a 1-bit port and the
group under which this port is driven.
evaluates to true. The operands to a while operation is a 1-bit port and an
optional combinational group under which this port is driven.

Note: The native and CIRCT Calyx IRs may diverge wrt. 'with' execution, see:
https://github.com/cucapra/calyx/discussions/588
Expand All @@ -150,9 +154,13 @@ def WhileOp : CalyxContainer<"while", [
calyx.enable @G2
...
}
calyx.while %1 {
calyx.enable @G2
...
}
```
}];

let assemblyFormat = "$cond `with` $groupName $body attr-dict";
let assemblyFormat = "$cond (`with` $groupName^)? $body attr-dict";
let verifier = "return ::verify$cppClass(*this);";
}
28 changes: 28 additions & 0 deletions include/circt/Dialect/Calyx/CalyxInterfaces.td
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,34 @@

include "mlir/IR/OpBase.td"

// Op interface for groups.
def GroupOpInterface : OpInterface<"GroupInterface"> {
let cppNamespace = "::circt::calyx";

let description = [{
This is an op interface for Calyx Groups.
}];

let methods = [
InterfaceMethod<
"This returns the symbol name of the group.",
"mlir::StringAttr",
"symName",
(ins), [{
Operation* op = $_op;
return op->getAttrOfType<mlir::StringAttr>("sym_name");
}]>,
InterfaceMethod<
"This returns the body of the group.",
"Block*",
"getBody",
(ins), [{
Operation* op = $_op;
return &op->getRegion(0).front();
}]>
];
}

/// Op Interface for cells.
def CellOpInterface : OpInterface<"CellInterface"> {
let cppNamespace = "::circt::calyx";
Expand Down
37 changes: 36 additions & 1 deletion include/circt/Dialect/Calyx/CalyxStructure.td
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ def InstanceOp : CalyxCell<"instance", [

def GroupOp : CalyxOp<"group", [
HasParent<"WiresOp">,
DeclareOpInterfaceMethods<GroupOpInterface>,
NoRegionArguments,
RegionKindInterface,
SingleBlock,
Expand Down Expand Up @@ -212,9 +213,43 @@ def GroupOp : CalyxOp<"group", [
let assemblyFormat = "$sym_name $body attr-dict";
}

def CombGroupOp : CalyxOp<"comb_group", [
HasParent<"WiresOp">,
DeclareOpInterfaceMethods<GroupOpInterface>,
NoRegionArguments,
RegionKindInterface,
SingleBlock,
Symbol,
NoTerminator
]> {
let summary = "Calyx Combinational Group";
let description = [{
Represents a Calyx combinational group, which is a collection
of combinational assignments that are only active when the group
is run from the control execution schedule.
A combinational group does not have group_go or group_done operators.

```mlir
calyx.comb_group @MyCombGroup {
calyx.assign %1 = %2 : i32
}
```
}];

let arguments = (ins SymbolNameAttr: $sym_name);

let extraClassDeclaration = [{
// Implement RegionKindInterface.
static RegionKind getRegionKind(unsigned index) { return RegionKind::Graph; }
}];

let regions = (region SizedRegion<1>:$body);
let assemblyFormat = "$sym_name $body attr-dict";
}

def AssignOp : CalyxOp<"assign", [
SameTypeConstraint<"dest", "src">,
ParentOneOf<["GroupOp", "WiresOp"]>
ParentOneOf<["GroupOp", "CombGroupOp", "WiresOp"]>
]> {
let summary = "Calyx Assignment";
let description = [{
Expand Down
69 changes: 47 additions & 22 deletions lib/Dialect/Calyx/CalyxOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,16 @@ static LogicalResult verifyControlBody(Operation *op) {
return success();
}

static LogicalResult portsDrivenByGroup(ValueRange ports, GroupOp groupOp);
static LogicalResult portsDrivenByGroup(ValueRange ports,
GroupInterface groupOp);

/// Checks whether @p port is driven from within @p groupOp.
static LogicalResult portDrivenByGroup(Value port, GroupOp groupOp) {
static LogicalResult portDrivenByGroup(Value port, GroupInterface groupOp) {
// Check if the port is driven by an assignOp from within @p groupOp.
for (auto &use : port.getUses()) {
if (auto assignOp = dyn_cast<AssignOp>(use.getOwner())) {
if (assignOp.dest() != port ||
assignOp->getParentOfType<GroupOp>() != groupOp)
assignOp->getParentOfType<GroupInterface>() != groupOp)
continue;
return success();
}
Expand All @@ -156,7 +157,8 @@ static LogicalResult portDrivenByGroup(Value port, GroupOp groupOp) {
}

/// Checks whether all ports in @p ports are driven from within @p groupOp
static LogicalResult portsDrivenByGroup(ValueRange ports, GroupOp groupOp) {
static LogicalResult portsDrivenByGroup(ValueRange ports,
GroupInterface groupOp) {
return success(llvm::all_of(ports, [&](auto port) {
return portDrivenByGroup(port, groupOp).succeeded();
}));
Expand Down Expand Up @@ -537,10 +539,10 @@ static LogicalResult verifyWiresOp(WiresOp wires) {

// Verify each group is referenced in the control section.
for (auto &&op : *wires.getBody()) {
if (!isa<GroupOp>(op))
if (!isa<GroupInterface>(op))
continue;
auto group = cast<GroupOp>(op);
auto groupName = group.sym_nameAttr();
auto group = cast<GroupInterface>(op);
auto groupName = group.symName();
if (SymbolTable::symbolKnownUseEmpty(groupName, control))
return op.emitOpError() << "with name: " << groupName
<< " is unused in the control execution schedule";
Expand Down Expand Up @@ -818,11 +820,16 @@ static LogicalResult verifyMemoryOp(MemoryOp memoryOp) {
static LogicalResult verifyEnableOp(EnableOp enableOp) {
auto component = enableOp->getParentOfType<ComponentOp>();
auto wiresOp = component.getWiresOp();
auto groupName = enableOp.groupName();
StringRef groupName = enableOp.groupName();

if (!wiresOp.lookupSymbol<GroupOp>(groupName))
auto groupOp = wiresOp.lookupSymbol<GroupInterface>(groupName);
if (!groupOp)
return enableOp.emitOpError()
<< "with group: " << groupName << ", which does not exist.";
<< "with group '" << groupName << "', which does not exist.";

if (isa<CombGroupOp>(groupOp))
Copy link
Member

Choose a reason for hiding this comment

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

Nice!!

return enableOp.emitOpError() << "with group '" << groupName
<< "', which is a combinational group.";

return success();
}
Expand All @@ -834,12 +841,6 @@ static LogicalResult verifyEnableOp(EnableOp enableOp) {
static LogicalResult verifyIfOp(IfOp ifOp) {
auto component = ifOp->getParentOfType<ComponentOp>();
auto wiresOp = component.getWiresOp();
auto groupName = ifOp.groupName();
auto groupOp = wiresOp.lookupSymbol<GroupOp>(groupName);

if (!groupOp)
return ifOp.emitOpError()
<< "with group '" << groupName << "', which does not exist.";

if (ifOp.thenRegion().front().empty())
return ifOp.emitError() << "empty 'then' region.";
Expand All @@ -848,10 +849,25 @@ static LogicalResult verifyIfOp(IfOp ifOp) {
ifOp.elseRegion().front().empty())
return ifOp.emitError() << "empty 'else' region.";

Optional<StringRef> optGroupName = ifOp.groupName();
if (!optGroupName.hasValue()) {
/// No combinational group was provided
return success();
}
StringRef groupName = optGroupName.getValue();
auto groupOp = wiresOp.lookupSymbol<GroupInterface>(groupName);
if (!groupOp)
return ifOp.emitOpError()
<< "with group '" << groupName << "', which does not exist.";

if (isa<GroupOp>(groupOp))
return ifOp.emitOpError() << "with group '" << groupName
<< "', which is not a combinational group.";

if (failed(portDrivenByGroup(ifOp.cond(), groupOp)))
return ifOp.emitError()
<< "conditional op: '" << valueName(component, ifOp.cond())
<< "' expected to be driven from group: '" << ifOp.groupName()
<< "' expected to be driven from group: '" << groupName
<< "' but no driver was found.";

return success();
Expand All @@ -863,20 +879,29 @@ static LogicalResult verifyIfOp(IfOp ifOp) {
static LogicalResult verifyWhileOp(WhileOp whileOp) {
auto component = whileOp->getParentOfType<ComponentOp>();
auto wiresOp = component.getWiresOp();
auto groupName = whileOp.groupName();
auto groupOp = wiresOp.lookupSymbol<GroupOp>(groupName);

if (whileOp.body().front().empty())
return whileOp.emitError() << "empty body region.";

Optional<StringRef> optGroupName = whileOp.groupName();
if (!optGroupName.hasValue()) {
/// No combinational group was provided
return success();
}
StringRef groupName = optGroupName.getValue();
auto groupOp = wiresOp.lookupSymbol<GroupInterface>(groupName);
if (!groupOp)
return whileOp.emitOpError()
<< "with group '" << groupName << "', which does not exist.";

if (whileOp.body().front().empty())
return whileOp.emitError() << "empty body region.";
if (isa<GroupOp>(groupOp))
return whileOp.emitOpError() << "with group '" << groupName
<< "', which is not a combinational group.";

if (failed(portDrivenByGroup(whileOp.cond(), groupOp)))
return whileOp.emitError()
<< "conditional op: '" << valueName(component, whileOp.cond())
<< "' expected to be driven from group: '" << whileOp.groupName()
<< "' expected to be driven from group: '" << groupName
<< "' but no driver was found.";

return success();
Expand Down
16 changes: 9 additions & 7 deletions lib/Dialect/Calyx/Export/CalyxEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ struct Emitter {
void emitWires(WiresOp op);

// Group emission
void emitGroup(GroupOp group);
void emitGroup(GroupInterface group);

// Control emission
void emitControl(ControlOp control);
Expand Down Expand Up @@ -265,10 +265,10 @@ struct Emitter {

/// Emits a port for a Group.
template <typename OpTy>
void emitGroupPort(GroupOp group, OpTy op, StringRef portHole) {
void emitGroupPort(GroupInterface group, OpTy op, StringRef portHole) {
assert((isa<GroupGoOp>(op) || isa<GroupDoneOp>(op)) &&
"Required to be a group port.");
indent() << group.sym_name() << LSquare() << portHole << RSquare()
indent() << group.symName().getValue() << LSquare() << portHole << RSquare()
<< equals();
if (op.guard()) {
emitValue(op.guard(), /*isIndented=*/false);
Expand Down Expand Up @@ -297,7 +297,8 @@ struct Emitter {
.template Case<IfOp, WhileOp>([&](auto op) {
indent() << (isa<IfOp>(op) ? "if " : "while ");
emitValue(op.cond(), /*isIndented=*/false);
os << " with " << op.groupName();
if (auto groupName = op.groupName(); groupName.hasValue())
Copy link
Member

Choose a reason for hiding this comment

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

Nice use of if statement with initializer :)

os << " with " << groupName.getValue();
emitCalyxBody([&]() { emitCalyxControl(op); });
})
.template Case<EnableOp>([&](auto op) { emitEnable(op); })
Expand Down Expand Up @@ -497,7 +498,7 @@ void Emitter::emitWires(WiresOp op) {
emitCalyxSection("wires", [&]() {
for (auto &&bodyOp : *op.getBody()) {
TypeSwitch<Operation *>(&bodyOp)
.Case<GroupOp>([&](auto op) { emitGroup(op); })
.Case<GroupInterface>([&](auto op) { emitGroup(op); })
.Case<AssignOp>([&](auto op) { emitAssignment(op); })
.Case<hw::ConstantOp, comb::AndOp, comb::OrOp, comb::XorOp>(
[&](auto op) { /* Do nothing. */ })
Expand All @@ -508,7 +509,7 @@ void Emitter::emitWires(WiresOp op) {
});
}

void Emitter::emitGroup(GroupOp group) {
void Emitter::emitGroup(GroupInterface group) {
auto emitGroupBody = [&]() {
for (auto &&bodyOp : *group.getBody()) {
TypeSwitch<Operation *>(&bodyOp)
Expand All @@ -522,7 +523,8 @@ void Emitter::emitGroup(GroupOp group) {
});
}
};
emitCalyxSection("group", emitGroupBody, group.sym_name());
auto prefix = Twine(isa<CombGroupOp>(group) ? "comb " : "") + "group";
emitCalyxSection(prefix.str(), emitGroupBody, group.symName().getValue());
}

void Emitter::emitEnable(EnableOp enable) {
Expand Down
Loading