-
Notifications
You must be signed in to change notification settings - Fork 356
[Comb] Add comb.reverse operation with Verilog Export #8758
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
base: main
Are you sure you want to change the base?
Conversation
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.
Very cool! Just a few nitpicks and ideas for simplifications.
|
||
let hasFolder = 1; | ||
let hasCanonicalizer = 1; | ||
let hasVerifier = 1; |
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.
Since the verifier just returns success()
, you can simplify the op by removing this hasVerifier
line and removing the verify
function implementation 🙂
|
||
if (auto reverseOp = dyn_cast_or_null<comb::ReverseOp>(exp.getDefiningOp())) { | ||
ps << "{<<{"; | ||
emitSubExpr(reverseOp.getInput(), LowestPrecedence); | ||
ps << "}}"; | ||
emittedExprs.insert(reverseOp); | ||
return {Symbol, IsUnsigned}; | ||
} |
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.
It would be fantastic if you could move this emission into a separate ExprEmitter::visitComb
function. We already have one such function per Comb operation. That will then properly insert sign casts, parenthesis, and other things around the streaming operator.
Since you are adding a Comb op, you'll also have to add the op to the Comb op visitor. The visitor is what calls the various visitComb
functions. You'll need to do this in two places:
lib/Dialect/Comb/CombOps.cpp
Outdated
mlir::LogicalResult comb::ReverseOp::verify() { | ||
return mlir::success(); | ||
} |
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.
Not needed 🙂
lib/Dialect/Comb/CombOps.cpp
Outdated
void comb::ReverseOp::inferResultRanges( | ||
llvm::ArrayRef<mlir::ConstantIntRanges> operandRanges, | ||
llvm::function_ref<void(mlir::Value, const mlir::ConstantIntRanges &)> | ||
setResultRangeFn) { | ||
setResultRangeFn(getResult(), operandRanges.front()); | ||
} |
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'm not sure if this implementation of inferResultRanges
is correct. As far as I understand it, that interface is supposed to compute upper and lower bounds for the integer value of the result. Since the ReverseOp
reverses the bits of a number and therefore changes its value, the upper and lower bounds of the result are not the same as that of the input. The bit reversal probably has the annoying effect of splitting a contiguous range of integer values into potentially multiple ones.
Do any of your passes rely on having the InferIntRangeInterface
implemented on this op? If not, I would delete this implementation and remove the DeclareOpInterfaceMethods<InferIntRangeInterface, ["inferResultRanges"]>
trait from the op itself.
// CHECK-LABEL: module test_reverse( | ||
// CHECK-NEXT: input [7:0] in, | ||
// CHECK-NEXT: output [7:0] out | ||
// CHECK-NEXT: ); | ||
// CHECK-EMPTY: | ||
// CHECK-NEXT: assign out = {<<{in}}; | ||
// CHECK-NEXT: endmodule | ||
|
||
hw.module @test_reverse(in %in: i8, out out: i8) { | ||
%reversed = comb.reverse %in : (i8) -> i8 | ||
hw.output %reversed : i8 | ||
} |
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.
Nice! Could you move this into https://github.com/llvm/circt/blob/main/test/Conversion/ExportVerilog/verilog-basic.mlir? That's where the other Comb operations are tested 🙂
test/Dialect/Comb/reverse.mlir
Outdated
// This test checks two things for comb.reverse: | ||
// 1. Constant folding: reversing a constant should be done at compile time. | ||
// 2. Canonicalization: the pattern reverse(reverse(x)) should be simplified to x. | ||
|
||
// CHECK-LABEL: hw.module @test_reverse_canonicalize | ||
// CHECK-SAME: (in %[[IN:.*]] : i8, out out_double_rev : i8, out out_const_rev : i8) | ||
|
||
// After canonicalization, there should be NO comb.reverse operations, | ||
// since reverse(reverse(x)) simplifies to x, and reverse(const) should be folded. | ||
// Therefore, we ensure there are no comb.reverse ops in the output: | ||
// CHECK-NOT: comb.reverse | ||
|
||
hw.module @test_reverse_canonicalize(in %in : i8, out out_double_rev : i8, out out_const_rev : i8) { | ||
|
||
// Apply reverse twice to test canonicalization | ||
%rev1 = comb.reverse %in : (i8) -> i8 | ||
%rev2 = comb.reverse %rev1 : (i8) -> i8 | ||
|
||
// Apply reverse on a constant to test folding | ||
%c13 = hw.constant 13 : i8 | ||
%rev_const = comb.reverse %c13 : (i8) -> i8 | ||
|
||
// Check outputs: after canonicalization, | ||
// out_double_rev should be wired directly to %in, | ||
// and out_const_rev should be wired to the folded constant (which is -80 in i8) | ||
hw.output %rev2, %rev_const : i8, i8 | ||
} | ||
|
||
// CHECK: %[[CST:.*]] = hw.constant -80 : i8 | ||
// CHECK: hw.output %[[IN]], %[[CST]] : i8, i8 |
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.
Could you move this into https://github.com/llvm/circt/blob/main/test/Dialect/Comb/canonicalization.mlir? That's where the Comb dialect canonicalization tests are located 🙂
let hasCanonicalizer = 1; | ||
let hasVerifier = 1; | ||
|
||
let assemblyFormat = "$input attr-dict `:` functional-type($input, $result)"; |
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 functional-type is not necessary as it has SameOperandsAndResultType
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.
Good point, you should be able to just use
`:` type($input)
here.
Thank you both, @fabianschuiki and @uenoku, for the helpful review. I've implemented all the suggested changes. Please let me know if there is anything else. 🙂 |
Hey @mafeguimaraes, thanks a lot! It looks like there are a few clang-format issues that CI isn't happy about. These are just code formatting guidelines. Check out "clang format patches display" in https://github.com/llvm/circt/actions/runs/16469136872/job/46573783262?pr=8758 for details. The other CI failure seems to be related to an accidental LLVM downgrade in your branch. Could you undo the LLVM submodule change in your branch such that it lines up again with what's on "main"? That should clear up the CI build. |
ab48db1
to
5c55a79
Compare
I've run clang-format to fix the style issues and reset the LLVM submodule to match main. Thank you, @fabianschuiki! |
Hey @mafeguimaraes, it looks like the LLVM submodule is still pointing to a different commit than what's on main, causing CI to break. Try something like the following to change the submodule to point to the same commit as is on main:
|
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.
Thanks! a couple minor nits, but looking good.
@@ -2598,6 +2599,7 @@ SubExprInfo ExprEmitter::emitSubExpr(Value exp, | |||
SubExprSignRequirement signRequirement, | |||
bool isSelfDeterminedUnsignedValue, | |||
bool isAssignmentLikeContext) { | |||
|
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: stray whitespace change
emitError(op, "SV attributes emission is unimplemented for the op"); | ||
|
||
ps << "{<<{"; | ||
emitSubExpr(op.getInput(), LowestPrecedence); |
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.
It's been a while since I dealt with this, but is LowestPrecedence needed? The expression is insides brackets. It might be worth checking the spec to avoid unnecessary parens being emitted.
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 are right, since the expression is inside the {<<{...}} streaming operator, it's in a new context and will never need to be parenthesized.
In this emitter, passing LowestPrecedence to the recursive emitSubExpr call is the idiomatic way to achieve exactly that and prevent any unnecessary parens from being added.
emitSubExpr(op.getInput(), LowestPrecedence); | ||
ps << "}}"; | ||
|
||
return {Symbol, IsUnsigned}; |
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.
Should this be concatenation precedence?
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.
The streaming operator {<<{...}} should indeed have the same precedence as a regular concatenation.
emitSubExpr(op.getInput(), LowestPrecedence); | ||
ps << "}}"; | ||
|
||
return {Symbol, IsUnsigned}; |
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'm having a hard time verifying unsigned results from the spec. It seems true, but I can't find it.
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.
Yes, I had the same doubt. From what I found, the SystemVerilog streaming operator ({<<{}}) preserves the bit pattern without applying any sign extension or interpretation, which effectively makes the result unsigned.
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! Very cool that we now have an op for this 😃
That's great to hear! Thank you so much for the feedback and for guiding me through the process. I'm happy I could contribute. 😃 |
Looks like there's a minor breakage in an LLHD pass associated with the CombVisitor. I think you just need to create a Interesting that this didn't show up for you in your local build 🤔 |
Good catch! My local build configuration didn't include the LLHD dialect, so I missed this dependency. I've added the necessary visitComb stub for ReverseOp to the CombInterpreter. Thanks for pointing it out! |
Looks like clang-format is unhappy about some trailing white space. Try running clang-format locally, or manually fix the whitespace 😃 |
fed503c
to
e51303c
Compare
@mafeguimaraes You can run the tests locally with |
This PR introduces a new operation to the Comb dialect:
comb.reverse
, which performs bitwise reversal (mirroring) of an integer value.It also adds Verilog export support for this operation using SystemVerilog’s streaming operator
{<<{}}
.Bit reversal is a common operation in hardware design, especially in signal processing and communication protocols.
Previously, reversing bits required generating many explicit
assign
statements.This new operation makes the IR cleaner and enables direct export to compact Verilog syntax.
Example
Original Verilog:
MLIR
%rev = comb.reverse %in : (i8) -> i8
Verilog (exported)
assign rev = {<<{in}};
Implementation Details
The implementation is divided into three main parts:
Operation Definition (
CombOps.td
,CombOps.cpp
):Comb_ReverseOp
is defined inCombOps.td
withPure
andSameOperandsAndResultType
traits.CombOps.cpp
includes:comb.reverse(comb.reverse(x))
tox
.verify()
for operand/result checks andinferResultType()
for result inference.Verilog Lowering (
ExportVerilog.cpp
):comb::ReverseOp
was added.{<<{in}}
, generating compact and readable Verilog design.Testing (
reverse.mlir, comb-reverse.mlir
):-canonicalize
.-export-verilog
andFileCheck
.