[Comb] Add comb.reverse operation (#8758)

This commit 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.
This commit is contained in:
Maria Fernanda Guimarães 2025-07-28 15:28:22 -03:00 committed by GitHub
parent 29f4e1311e
commit 2f0ca3c18e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 135 additions and 21 deletions

View File

@ -38,7 +38,7 @@ public:
// Reduction Operators
ParityOp,
// Other operations.
ConcatOp, ReplicateOp, ExtractOp, MuxOp>(
ConcatOp, ReplicateOp, ExtractOp, MuxOp, ReverseOp>(
[&](auto expr) -> ResultType {
return thisCast->visitComb(expr, args...);
})
@ -104,6 +104,7 @@ public:
HANDLE(ReplicateOp, Unhandled);
HANDLE(ExtractOp, Unhandled);
HANDLE(MuxOp, Unhandled);
HANDLE(ReverseOp, Unary);
#undef HANDLE
};

View File

@ -331,3 +331,28 @@ def TruthTableOp : CombOp<"truth_table", [Pure]> {
let hasVerifier = 1;
}
def ReverseOp : CombOp<"reverse", [
SameOperandsAndResultType,
Pure
]> {
let summary = "Reverses the bit order of an integer value";
let description = [{
Reverses the bit ordering of a value. The LSB becomes the MSB and vice versa.
Example:
```mlir
%out = comb.reverse %in : i4
```
If %in is 4'b1101, then %out is 4'b1011.
}];
let arguments = (ins HWIntegerType:$input);
let results = (outs HWIntegerType:$result);
let hasFolder = 1;
let hasCanonicalizer = 1;
let assemblyFormat = "$input attr-dict `:` type($input)";
}

View File

@ -2379,6 +2379,7 @@ private:
// Comb Dialect Operations
using CombinationalVisitor::visitComb;
SubExprInfo visitComb(MuxOp op);
SubExprInfo visitComb(ReverseOp op);
SubExprInfo visitComb(AddOp op) {
assert(op.getNumOperands() == 2 && "prelowering should handle variadics");
return emitBinary(op, Addition, "+");
@ -3304,6 +3305,17 @@ SubExprInfo ExprEmitter::visitComb(MuxOp op) {
});
}
SubExprInfo ExprEmitter::visitComb(ReverseOp op) {
if (hasSVAttributes(op))
emitError(op, "SV attributes emission is unimplemented for the op");
ps << "{<<{";
emitSubExpr(op.getInput(), LowestPrecedence);
ps << "}}";
return {Symbol, IsUnsigned};
}
SubExprInfo ExprEmitter::printStructCreate(
ArrayRef<hw::detail::FieldInfo> fieldInfos,
llvm::function_ref<void(const hw::detail::FieldInfo &, unsigned)> fieldFn,

View File

@ -500,6 +500,45 @@ LogicalResult ConcatOp::inferReturnTypes(
return success();
}
//===----------------------------------------------------------------------===//
// ReverseOp
//===----------------------------------------------------------------------===//
// Folding of ReverseOp: if the input is constant, compute the reverse at
// compile time.
OpFoldResult comb::ReverseOp::fold(FoldAdaptor adaptor) {
// Try to cast the input attribute to an IntegerAttr.
auto cstInput = llvm::dyn_cast_or_null<mlir::IntegerAttr>(adaptor.getInput());
if (!cstInput)
return {};
APInt val = cstInput.getValue();
APInt reversedVal = val.reverseBits();
return mlir::IntegerAttr::get(getType(), reversedVal);
}
namespace {
struct ReverseOfReverse : public OpRewritePattern<comb::ReverseOp> {
using OpRewritePattern<comb::ReverseOp>::OpRewritePattern;
LogicalResult matchAndRewrite(comb::ReverseOp op,
PatternRewriter &rewriter) const override {
auto inputOp = op.getInput().getDefiningOp<comb::ReverseOp>();
if (!inputOp)
return failure();
rewriter.replaceOp(op, inputOp.getInput());
return success();
}
};
} // namespace
void comb::ReverseOp::getCanonicalizationPatterns(RewritePatternSet &results,
MLIRContext *context) {
results.add<ReverseOfReverse>(context);
}
//===----------------------------------------------------------------------===//
// Other Operations
//===----------------------------------------------------------------------===//

View File

@ -153,6 +153,7 @@ public:
void visitComb(comb::ReplicateOp op) { visitInvalidComb(op); }
void visitComb(comb::ExtractOp op) { visitInvalidComb(op); }
void visitComb(comb::MuxOp op) { visitInvalidComb(op); }
void visitComb(comb::ReverseOp op) { visitInvalidComb(op); }
private:
DenseMap<Value, APInt> results;

View File

@ -16,6 +16,7 @@ hw.module @no_ports() {
}
// CHECK-LABEL: module Expressions(
// CHECK-NEXT: input [7:0] in8,
// CHECK-NEXT: input [3:0] in4,
// CHECK-NEXT: input clock,
// CHECK-NEXT: output out1a,
@ -31,15 +32,15 @@ hw.module @no_ports() {
// CHECK-NEXT: out16s,
// CHECK-NEXT: output [16:0] sext17,
// CHECK-NEXT: output [1:0] orvout,
// CHECK-NEXT: output [7:0] out_reverse,
// CHECK-NEXT: [63:0] outTime,
// CHECK-NEXT: [31:0] outSTime
// CHECK-NEXT: );
hw.module @Expressions(in %in4: i4, in %clock: i1,
hw.module @Expressions(in %in8: i8, in %in4: i4, in %clock: i1,
out out1a: i1, out out1b: i1, out out1c: i1,
out out1d: i1, out out1e: i1, out out1f: i1, out out1g: i1,
out out4: i4, out out4s: i4, out out16: i16, out out16s: i16,
out sext17: i17, out orvout: i2, out outTime: i64, out outSTime:i32) {
out sext17: i17, out orvout: i2, out out_reverse: i8, out outTime: i64, out outSTime:i32) {
%c1_i4 = hw.constant 1 : i4
%c2_i4 = hw.constant 2 : i4
%c3_i4 = hw.constant 3 : i4
@ -84,6 +85,7 @@ hw.module @Expressions(in %in4: i4, in %clock: i1,
%17 = comb.or %6, %5 : i2
%18 = comb.concat %c0_i2, %in4 : i2, i4
// CHECK: wire [15:0] w2;
// CHECK: assign w2 = {6'h0, in4, clock, clock, in4};
// CHECK: assign w2 = {10'h0, {2'h0, in4} ^ {{..}}2{in4[3]}}, in4} ^ {6{clock}}};
%tmp = comb.extract %in4 from 3 : (i4) -> i1
@ -122,42 +124,45 @@ hw.module @Expressions(in %in4: i4, in %clock: i1,
%w3 = sv.wire : !hw.inout<i16>
%w3_use = sv.read_inout %w3 : !hw.inout<i16>
// CHECK-DAG: assign out1a = ^in4;
%p_res = comb.parity %in4 : i4
// CHECK-DAG: assign out1b = &in4;
%and_res = comb.icmp eq %in4, %c-1_i4 : i4
// CHECK-DAG: assign out1c = |in4;
%or_res = comb.icmp ne %in4, %c0_i4 : i4
// CHECK: assign out1a = ^in4;
%0 = comb.parity %in4 : i4
// CHECK: assign out1b = &in4;
%1 = comb.icmp eq %in4, %c-1_i4 : i4
// CHECK: assign out1c = |in4;
%2 = comb.icmp ne %in4, %c0_i4 : i4
// CHECK: assign out1d = in4 === 4'h0;
// CHECK-DAG: assign out1d = in4 === 4'h0;
%cmp3 = comb.icmp ceq %in4, %c0_i4 : i4
// CHECK: assign out1e = in4 !== 4'h0;
// CHECK-DAG: assign out1e = in4 !== 4'h0;
%cmp4 = comb.icmp cne %in4, %c0_i4 : i4
// CHECK: assign out1f = in4 ==? 4'h0;
// CHECK-DAG: assign out1f = in4 ==? 4'h0;
%cmp5 = comb.icmp weq %in4, %c0_i4 : i4
// CHECK: assign out1g = in4 !=? 4'h0;
// CHECK-DAG: assign out1g = in4 !=? 4'h0;
%cmp6 = comb.icmp wne %in4, %c0_i4 : i4
// CHECK: assign out4s = $signed($signed(in4) >>> in4);
// CHECK: assign sext17 = {w3[15], w3};
// CHECK-DAG: assign out4s = $signed($signed(in4) >>> in4);
// CHECK-DAG: assign sext17 = {w3[15], w3};
%36 = comb.extract %w3_use from 15 : (i16) -> i1
%35 = comb.concat %36, %w3_use : i1, i16
// Variadic with name attribute lowers
// CHECK: assign orvout = in4[1:0] | in4[3:2] | in4[2:1];
// CHECK-DAG: assign orvout = in4[1:0] | in4[3:2] | in4[2:1];
%orpre1 = comb.extract %in4 from 0 : (i4) -> i2
%orpre2 = comb.extract %in4 from 2 : (i4) -> i2
%orpre3 = comb.extract %in4 from 1 : (i4) -> i2
%orv = comb.or %orpre1, %orpre2, %orpre3 {sv.namehint = "hintyhint"}: i2
// Test for comb.reverse
// CHECK-DAG: assign out_reverse = {<<{in8}};
%rev8 = comb.reverse %in8 : i8
// Time system functions
// CHECK: assign outTime = $time;
// CHECK-DAG: assign outTime = $time;
%time = sv.system.time : i64
// CHECK: assign outSTime = $stime;
// CHECK-DAG: assign outSTime = $stime;
%stime = sv.system.stime : i32
hw.output %0, %1, %2, %cmp3, %cmp4, %cmp5, %cmp6, %w1_use, %11, %w2_use, %w3_use, %35, %orv, %time, %stime : i1, i1, i1, i1, i1, i1, i1, i4, i4, i16, i16, i17, i2, i64, i32
hw.output %p_res, %and_res, %or_res, %cmp3, %cmp4, %cmp5, %cmp6, %w1_use, %11, %w2_use, %w3_use, %35, %orv, %rev8, %time, %stime : i1, i1, i1, i1, i1, i1, i1, i4, i4, i16, i16, i17, i2, i8, i64, i32
}
// CHECK-LABEL: module Precedence(

View File

@ -1809,3 +1809,34 @@ hw.module @ShrSWideZeroShift(in %a: i1000, out y: i1000) {
%1 = comb.shrs %a, %0 : i1000
hw.output %1 : i1000
}
// 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
%rev2 = comb.reverse %rev1 : i8
// Apply reverse on a constant to test folding
%c13 = hw.constant 13 : i8
%rev_const = comb.reverse %c13 : 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