mirror of https://github.com/llvm/circt.git
[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:
parent
29f4e1311e
commit
2f0ca3c18e
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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)";
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue