-
Notifications
You must be signed in to change notification settings - Fork 299
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
Support scf.if
Op Lowering to Calyx
#6256
Conversation
It would be helpful to have some minimal code examples of what you expect and what you want. |
|
||
if (isThenRegion) { | ||
// create a calyx group for the then branch | ||
std::string groupName = "then_group"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll want to make sure there are no name clashes. See, e.g.,
circt/lib/Conversion/CalyxToFSM/CalyxToFSM.cpp
Lines 305 to 310 in 307fb58
// The following loop is used to check if the transitionName already exists. | |
// If it does, the loop regenerates the transitionName. | |
do { | |
transitionName = transitionNameHead + std::to_string(transitionNameTail++); | |
} while (component.getWiresOp().lookupSymbol(transitionName)); | |
return transitionName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a good start, thanks for working on this!
I'm not sure why you are seeing the GroupOp body is void. The GroupOp builder, which is called by createGroup, should create a block, and buildAssignmentsForRegisterWrite should add assign ops to that block.
} | ||
else if (auto ifOp = dyn_cast<scf::IfOp>(yieldOp->getParentOp())) { | ||
|
||
ScfIfOp scfIfOp(ifOp); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is used, is it intended to be used? I think in general, we probably don't need the interfaces you've added here, if there is only one thing implementing the interface. The reason we have interfaces for the while op is because we want to generically handle both scf::WhileOp and our own loopschedule::WhileOp.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you are right, adding an interface is redundant for if
Sure! I'm working on to make the example in the mentioned issue working: func.func @main(%arg0 : i32, %arg1 : i32) -> i32 {
%0 = arith.cmpi slt, %arg0, %arg1 : i32
%1 = scf.if %0 -> i32 {
%3 = arith.addi %arg0, %arg1 : i32
scf.yield %3 : i32
} else {
scf.yield %arg1 : i32
}
} in which I try prepare the registers and to |
Hi all, I thought it'd be nice if I take a step back and first decide what's my expected lowered IR would look like. func.func @main(%arg0 : i32, %arg1 : i32) -> i32 {
%0 = arith.cmpi slt, %arg0, %arg1 : i32
%1 = scf.if %0 -> i32 {
%2 = arith.addi %arg0, %arg1 : i32
scf.yield %2 : i32
} else {
scf.yield %arg1 : i32
}
return %1 : i32
} After some research, I think my desired IR after lowering would be something like: module attributes {calyx.entrypoint = "main"} {
calyx.component @main(%in0: i32, %in1: i32, %clk: i1 {clk}, %reset: i1 {reset}, %go: i1 {go}) -> (%out0: i32, %done: i1 {done}) {
%std_slt_0.left, %std_slt_0.right, %std_slt_0.out = calyx.std_slt @std_slt_0 : i32, i32, i1
%std_add_0.left, %std_add_0.right, %std_add_0.out = calyx.std_add @std_add_0 : i32, i32, i32
%yield_reg.in, %yield_reg.write_en, %yield_reg.clk, %yield_reg.reset, %yield_reg.out, %yield_reg.done = calyx.register @yield_reg : i32, i1, i1, i1, i32, i1
%ret_reg.in, %ret_reg.write_en, %ret_reg.clk, %ret_reg.reset, %ret_reg.out, %ret_reg.done = calyx.register @ret_reg : i32, i1, i1, i1, i32, i1
calyx.wires {
calyx.assign %out = %ret_reg.out : i32
calyx.comb_group @bb0_0 {
calyx.assign %std_slt.left = %arg0 : i32
calyx.assign %std_slt.right = %arg1 : i32
}
calyx.comb_group @bb0_1 {
calyx.assign %std_add.left = %arg0 : i32
calyx.assign %std_add.right = %arg1 : i32
}
calyx.group @assign_then_block {
calyx.assign %yield_reg.in = %std_add.out : i32
calyx.assign %yield_reg.write_en = %true : i1
calyx.group_done %yield_reg.done : i1
}
calyx.group @assign_else_block {
calyx.assign %yield_reg.in = %arg1 : i32
calyx.assign %yield_reg.write_en = %true : i1
calyx.group_done %yield_reg.done : i1
}
calyx.group @ret_assign {
calyx.assign %ret_reg.in = %yield_reg.out : i32
calyx.assign %ret_reg.write_en = %true : i1
calyx.group_done %ret_reg.done : i1
}
}
calyx.control {
calyx.seq {
calyx.if %std_slt.out with @bb0_1 {
calyx.seq {
calyx.enable @assign_then_block
}
}
calyx.else {
calyx.enable @assign_else_block
}
}
}
} {toplevel}
}
} A few things I'm confused/not confident about are:
Any insight will be greatly appreciated! :) |
I think the expected IR looks good. You'll want to actually use the
Can you elaborate? Do you have an example where just using Calyx control wouldn't work?
IMO, I think a good first step is just creating a yield register and getting that to work. Then, you can discuss more optimal design decisions, e.g., maybe you'd want to canonicalize such simple use cases with a |
Agreed with @cgyurgyik! I think we should try getting something working first and worry about optimizing the encoding later on. @jiahanxie353 answers to your questions:
The way
This encoding looks good to me! Calyx's
I don't see any problems with it! |
Thanks for all the advice @cgyurgyik @rachitnigam !
calyx.group @assign_then_block {
calyx.assign %yield_reg.in = %std_add.out : i32
calyx.assign %yield_reg.write_en = %true : i1
calyx.group_done %yield_reg.done : i1
} but now the group's body is empty. Thanks! |
First, I think the So you're creating assignment operations and they're not appearing inside the block's body? Where are they appearing? What you probably want to do is set the insertion point correctly, e.g.,
https://mlir.llvm.org/doxygen/classmlir_1_1OpBuilder.html Example in this project: |
Progress
Thanks for pointing out, I was confused by these two.
Thanks to @cgyurgyik advice, they are now appearing in the correct position! Shown below: // -----// IR Dump After SCFToCalyx Failed (lower-scf-to-calyx) //----- //
"builtin.module"() ({
"calyx.component"() ({
^bb0(%arg0: i32, %arg1: i32, %arg2: i1, %arg3: i1, %arg4: i1, %arg5: i32, %arg6: i1):
%0 = "hw.constant"() {value = true} : () -> i1
%1:6 = "calyx.register"() {sym_name = "cond_arg_reg"} : () -> (i1, i1, i1, i1, i1, i1)
%2:6 = "calyx.register"() {sym_name = "else_yield_reg"} : () -> (i32, i1, i1, i1, i32, i1)
%3:6 = "calyx.register"() {sym_name = "then_yield_reg"} : () -> (i32, i1, i1, i1, i32, i1)
%4:3 = "calyx.std_add"() {sym_name = "std_add_0"} : () -> (i32, i32, i32)
%5:3 = "calyx.std_slt"() {sym_name = "std_slt_0"} : () -> (i32, i32, i1)
%6:6 = "calyx.register"() {sym_name = "ret_arg0_reg"} : () -> (i32, i1, i1, i1, i32, i1)
"calyx.wires"() ({
"calyx.assign"(%1#0, %5#2) : (i1, i1) -> ()
"calyx.assign"(%arg5, %6#4) : (i32, i32) -> ()
"calyx.comb_group"() ({
"calyx.assign"(%5#0, %arg0) : (i32, i32) -> ()
"calyx.assign"(%5#1, %arg1) : (i32, i32) -> ()
}) {sym_name = "bb0_0"} : () -> ()
"calyx.comb_group"() ({
"calyx.assign"(%4#0, %arg0) : (i32, i32) -> ()
"calyx.assign"(%4#1, %arg1) : (i32, i32) -> ()
}) {sym_name = "bb0_1"} : () -> ()
"calyx.group"() ({
"calyx.assign"(%3#0, %4#0) : (i32, i32) -> ()
"calyx.assign"(%3#1, %0) : (i1, i1) -> ()
"calyx.group_done"(%3#5) : (i1) -> ()
}) {sym_name = "assign_then_group"} : () -> ()
"calyx.group"() ({
"calyx.assign"(%2#0, %arg1) : (i32, i32) -> ()
"calyx.assign"(%2#1, %0) : (i1, i1) -> ()
"calyx.group_done"(%2#5) : (i1) -> ()
}) {sym_name = "assign_else_group"} : () -> ()
}) : () -> ()
"calyx.control"() ({
^bb0:
}) : () -> ()
}) {function_type = (i32, i32, i1, i1, i1, i32, i1) -> (), portAttributes = [{}, {}, {clk}, {reset}, {go}, {}, {done}], portDirections = -32 : i7, portNames = ["in0", "in1", "clk", "reset", "go", "out0", "done"], sym_name = "main", toplevel} : () -> ()
"func.func"() <{function_type = (i32, i32) -> i32, sym_name = "func_main"}> ({
^bb0(%arg0: i32, %arg1: i32):
%0 = "scf.if"(%5#2) ({
%1 = "arith.addi"(%arg0, %arg1) : (i32, i32) -> i32
"scf.yield"(%4#2) : (i32) -> ()
}, {
"scf.yield"(%arg1) : (i32) -> ()
}) : (i1) -> i32
"func.return"(%0) : (i32) -> ()
}) : () -> ()
}) {calyx.entrypoint = "main"} : () -> ()
TodoI was not able to get the return assignments working as well as the Calyx control. After I added
Seems like |
For example [1], // 1. Retrieve the conditional SSA value.
auto cond = whileOp.getConditionValue();
// Get the group under which it is assigned.
auto condGroup = getState<ComponentLoweringState>()
.getEvaluatingGroup<calyx::CombGroupOp>(cond);
// Then, get its symbol (this is what a calyx::WhileOp builder needs).
auto symbolAttr = FlatSymbolRefAttr::get(
StringAttr::get(getContext(), condGroup.getSymName()));
// Lastly, pass that into the calyx::WhileOp.
return rewriter.create<calyx::WhileOp>(loc, cond, symbolAttr);
} You're trying to get the evaluating group of some SSA value that does not have a group, or wasn't properly register here. To make progress, I would suggest finding where this call is coming from, and what the value is. 1: circt/lib/Conversion/SCFToCalyx/SCFToCalyx.cpp Lines 1476 to 1483 in d4bc36f
|
Thanks for the advice! I got almost everything working now excepting Calyx control. Right now I only have enabled // -----// IR Dump After SCFToCalyx Failed (lower-scf-to-calyx) //----- //
"builtin.module"() ({
"calyx.component"() ({
^bb0(%arg0: i32, %arg1: i32, %arg2: i1, %arg3: i1, %arg4: i1, %arg5: i32, %arg6: i1):
%0 = "hw.constant"() {value = true} : () -> i1
%1:6 = "calyx.register"() {sym_name = "cond_arg_reg"} : () -> (i1, i1, i1, i1, i1, i1)
%2:6 = "calyx.register"() {sym_name = "else_yield_0_reg"} : () -> (i32, i1, i1, i1, i32, i1)
%3:6 = "calyx.register"() {sym_name = "then_yield_0_reg"} : () -> (i32, i1, i1, i1, i32, i1)
%4:3 = "calyx.std_add"() {sym_name = "std_add_0"} : () -> (i32, i32, i32)
%5:3 = "calyx.std_slt"() {sym_name = "std_slt_0"} : () -> (i32, i32, i1)
%6:6 = "calyx.register"() {sym_name = "ret_arg0_reg"} : () -> (i32, i1, i1, i1, i32, i1)
"calyx.wires"() ({
"calyx.assign"(%1#0, %5#2) : (i1, i1) -> ()
"calyx.assign"(%arg5, %6#4) : (i32, i32) -> ()
"calyx.group"() ({
"calyx.assign"(%3#0, %4#0) : (i32, i32) -> ()
"calyx.assign"(%3#1, %0) : (i1, i1) -> ()
"calyx.group_done"(%3#5) : (i1) -> ()
}) {sym_name = "assign_then_group"} : () -> ()
"calyx.group"() ({
"calyx.assign"(%2#0, %arg1) : (i32, i32) -> ()
"calyx.assign"(%2#1, %0) : (i1, i1) -> ()
"calyx.group_done"(%2#5) : (i1) -> ()
}) {sym_name = "assign_else_group"} : () -> ()
"calyx.group"() ({
"calyx.assign"(%6#0, %3#4) : (i32, i32) -> ()
"calyx.assign"(%6#1, %0) : (i1, i1) -> ()
"calyx.group_done"(%6#5) : (i1) -> ()
}) {sym_name = "ret_assign_0"} : () -> ()
}) : () -> ()
"calyx.control"() ({
"calyx.seq"() ({
"calyx.enable"() {groupName = @ret_assign_0} : () -> ()
}) : () -> ()
}) : () -> ()
}) {function_type = (i32, i32, i1, i1, i1, i32, i1) -> (), portAttributes = [{}, {}, {clk}, {reset}, {go}, {}, {done}], portDirections = -32 : i7, portNames = ["in0", "in1", "clk", "reset", "go", "out0", "done"], sym_name = "main", toplevel} : () -> ()
}) {calyx.entrypoint = "main"} : () -> ()
And the verifier is complaining error: 'calyx.group' op with name: "assign_then_group" is unused in the control execution schedule
%1 = scf.if %0 -> i32 {
^
test.mlir:3:8: note: see current operation:
"calyx.group"() ({
"calyx.assign"(%3#0, %4#0) : (i32, i32) -> ()
"calyx.assign"(%3#1, %0) : (i1, i1) -> ()
"calyx.group_done"(%3#5) : (i1) -> ()
}) {sym_name = "assign_then_group"} : () -> () To proceed, I guess I have to support |
No, it’s complaining that compilation generated a group that is not used by the calyx control program. This means you generated the group but didn’t add it to the control program |
I see. Can you point me to the right direction of adding to the control program? I thought I should use |
I am not at the computer but IIRC there is some |
Yes! Working on it! |
Hi, I've working on adding control to When I'm Specifically, it could not find the evaluating group for:
if you this information is helpful. However, as I have print statements whenever I register the evaluating group, I'm pretty sure that I registered this
I've been stuck for a while an any insight will be much appreciated. Thank you! |
22375cc
to
7b9486b
Compare
Hi folks, Just finished almost everything of supporting One thing that is still missing is Any suggestions? Should I only be using |
I think it would be possible to update lateSSAReplacement to look at the data you added to both thenRegs and elseRegs. I need to take another look at the PR to see what makes sense, but in general, it should be fine to adapt lateSSAReplacement to the changes you've made here. |
|
||
calyx::RegisterOp getThenYieldRegs(scf::IfOp op, unsigned idx) { | ||
auto regs = getThenYieldRegs(op); | ||
auto it = regs.find(idx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the find is only used in an assert, it's fine to do
assert(regs.count(idx) ....);
return regs[idx];
calyx::RegisterOp getElseYieldRegs(scf::IfOp op, unsigned idx) { | ||
auto regs = getElseYieldRegs(op); | ||
auto it = regs.find(idx); | ||
assert(it != regs.end() && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Again, we aren't worried about the assertion path and generally lean towards shorter code.
7b9486b
to
2564b93
Compare
1d1c82b
to
171e70a
Compare
Marking ready for review again. And pretty sure it's correct this time. |
LGTM on my end! @cgyurgyik any final C++ comments? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, nice work. Just one small nit.
getState<ComponentLoweringState>().setElseGroup(scfIfOp, elseGroupOp); | ||
} | ||
|
||
for (auto res : scfIfOp.getResults()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This is confusing since you already have a res
variable above. Can you rename this please?
171e70a
to
7618f07
Compare
This PR tries to add support for
scf.if
during lowering to Calyx, as mentioned in this issue #4843Currently this PR is still in early stages and contains some issues, but I believe that some guidance would help ensure I'm on the right track!
My Thoughts and Progress
To add support for
scf.if
, I think we need:if
op lowering;Challenges/Issues
goupOp
is empty and only hasvoid
in the body, as in this line, so I couldn't move on to debug other issues (but ofc this approach could be wrong in the first place).I understand that this is still a work-in-progress, and there are several rough edges. I'm hoping to get some guidance on the general direction and any potential pitfalls I might encounter. Any feedback, suggestions, or advice would be greatly appreciated!