Skip to content

Commit

Permalink
[SV] Implement a canonicalizer to merge neighboring procedural assign…
Browse files Browse the repository at this point in the history
…ments (llvm#4074)

This PR implements a canonicalizer to merge neighboring procedural assignments, e.g:
```
a[0] <= b[1]
a[1] <= b[2]
a[2] <= b[3]

->
a[2:0] <= b[3:1]
```

For a procedural assignment, we first check the next operation is the same kind of an assignment. If so, we check both of dest values are array indexing inout operations that point at neighboring elements.
  • Loading branch information
uenoku authored Nov 1, 2022
1 parent e125fd7 commit e477486
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 1 deletion.
3 changes: 3 additions & 0 deletions include/circt/Dialect/HW/HWOps.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ StringAttr getArgSym(Operation *op, unsigned i);
/// argument.
StringAttr getResultSym(Operation *op, unsigned i);

// Check whether an integer value is an offset from a base.
bool isOffset(Value base, Value index, uint64_t offset);

// A class for providing access to the in- and output ports of a module through
// use of the HWModuleBuilder.
class HWModulePortAccessor {
Expand Down
2 changes: 2 additions & 0 deletions include/circt/Dialect/SV/SVStatements.td
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ def BPAssignOp : SVOp<"bpassign", [InOutTypeConstraint<"src", "dest">,
occur in initial, always, task, and function blocks. The statement is
executed before any following statements are. See SV Spec 10.4.1.
}];
let hasCanonicalizeMethod = true;
let arguments = (ins InOutType:$dest, InOutElementType:$src);
let results = (outs);
let assemblyFormat = "$dest `,` $src attr-dict `:` qualified(type($src))";
Expand All @@ -427,6 +428,7 @@ def PAssignOp : SVOp<"passign", [InOutTypeConstraint<"src", "dest">,
These occur in initial, always, task, and function blocks. The statement
can be scheduled without blocking procedural flow. See SV Spec 10.4.2.
}];
let hasCanonicalizeMethod = true;
let arguments = (ins InOutType:$dest, InOutElementType:$src);
let results = (outs);
let assemblyFormat = "$dest `,` $src attr-dict `:` qualified(type($src))";
Expand Down
2 changes: 1 addition & 1 deletion lib/Dialect/HW/HWOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1541,7 +1541,7 @@ LogicalResult ArrayCreateOp::verify() {
}

// Check whether an integer value is an offset from a base.
static bool isOffset(Value base, Value index, uint64_t offset) {
bool hw::isOffset(Value base, Value index, uint64_t offset) {
if (auto constBase = base.getDefiningOp<hw::ConstantOp>()) {
if (auto constIndex = index.getDefiningOp<hw::ConstantOp>()) {
// If both values are a constant, check if index == base + offset.
Expand Down
129 changes: 129 additions & 0 deletions lib/Dialect/SV/SVOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/TypeSwitch.h"

#include <optional>

using namespace circt;
using namespace sv;
using mlir::TypedAttr;
Expand Down Expand Up @@ -1055,6 +1057,133 @@ LogicalResult PAssignOp::verify() {
return success();
}

namespace {
// This represents a slice of an array.
struct ArraySlice {
Value array;
Value start;
size_t size; // Represent a range array[start, start + size).

// Get a struct from the value. Return std::nullopt if the value doesn't
// represent an array slice.
static std::optional<ArraySlice> getArraySlice(Value v) {
auto *op = v.getDefiningOp();
if (!op)
return std::nullopt;
return TypeSwitch<Operation *, std::optional<ArraySlice>>(op)
.Case<hw::ArrayGetOp, ArrayIndexInOutOp>(
[](auto arrayIndex) -> std::optional<ArraySlice> {
hw::ConstantOp constant =
arrayIndex.getIndex()
.template getDefiningOp<hw::ConstantOp>();
if (!constant)
return std::nullopt;
return ArraySlice{/*array=*/arrayIndex.getInput(),
/*start=*/constant,
/*end=*/1};
})
.Case<hw::ArraySliceOp>([](hw::ArraySliceOp slice)
-> std::optional<ArraySlice> {
auto constant = slice.getLowIndex().getDefiningOp<hw::ConstantOp>();
if (!constant)
return std::nullopt;
return ArraySlice{
/*array=*/slice.getInput(), /*start=*/constant,
/*end=*/hw::type_cast<hw::ArrayType>(slice.getType()).getSize()};
})
.Case<sv::IndexedPartSelectInOutOp>(
[](sv::IndexedPartSelectInOutOp index)
-> std::optional<ArraySlice> {
auto constant = index.getBase().getDefiningOp<hw::ConstantOp>();
if (!constant || index.getDecrement())
return std::nullopt;
return ArraySlice{/*array=*/index.getInput(),
/*start=*/constant,
/*end=*/index.getWidth()};
})
.Default([](auto) { return std::nullopt; });
}

// Create a pair of ArraySlice from source and destination of assignments.
static std::optional<std::pair<ArraySlice, ArraySlice>>
getAssignedRange(Operation *op) {
assert((isa<PAssignOp, BPAssignOp>(op) && "assignments are expected"));
auto srcRange = ArraySlice::getArraySlice(op->getOperand(1));
if (!srcRange)
return std::nullopt;
auto destRange = ArraySlice::getArraySlice(op->getOperand(0));
if (!destRange)
return std::nullopt;

return std::make_pair(*destRange, *srcRange);
}
};
} // namespace

// This canonicalization merges neiboring assignments of array elements into
// array slice assignments. e.g.
// a[0] <= b[1]
// a[1] <= b[2]
// ->
// a[1:0] <= b[2:1]
template <typename AssignTy>
static LogicalResult mergeNeiboringAssignments(AssignTy op,
PatternRewriter &rewriter) {
// Get assigned ranges of each assignment.
auto assignedRangeOpt = ArraySlice::getAssignedRange(op);
if (!assignedRangeOpt)
return failure();

auto [dest, src] = *assignedRangeOpt;
AssignTy nextAssign = dyn_cast_or_null<AssignTy>(op->getNextNode());
bool changed = false;
SmallVector<Location> loc{op.getLoc()};
// Check that a next operation is a same kind of the assignment.
while (nextAssign) {
auto nextAssignedRange = ArraySlice::getAssignedRange(nextAssign);
if (!nextAssignedRange)
break;
auto [nextDest, nextSrc] = *nextAssignedRange;
// Check that these assignments are mergaable.
if (dest.array != nextDest.array || src.array != nextSrc.array ||
!hw::isOffset(dest.start, nextDest.start, dest.size) ||
!hw::isOffset(src.start, nextSrc.start, src.size))
break;

dest.size += nextDest.size;
src.size += nextSrc.size;
changed = true;
loc.push_back(nextAssign.getLoc());
rewriter.eraseOp(nextAssign);
nextAssign = dyn_cast_or_null<AssignTy>(op->getNextNode());
}

if (!changed)
return failure();

// From here, construct assignments of array slices.
auto resultType = hw::ArrayType::get(
hw::type_cast<hw::ArrayType>(src.array.getType()).getElementType(),
src.size);
auto newDest = rewriter.create<sv::IndexedPartSelectInOutOp>(
op.getLoc(), dest.array, dest.start, dest.size);
auto newSrc = rewriter.create<hw::ArraySliceOp>(op.getLoc(), resultType,
src.array, src.start);
auto newLoc = rewriter.getFusedLoc(loc);
auto newOp = rewriter.replaceOpWithNewOp<AssignTy>(op, newDest, newSrc);
newOp->setLoc(newLoc);
return success();
}

LogicalResult PAssignOp::canonicalize(PAssignOp op, PatternRewriter &rewriter) {
return mergeNeiboringAssignments(op, rewriter);
}

LogicalResult BPAssignOp::canonicalize(BPAssignOp op,
PatternRewriter &rewriter) {
return mergeNeiboringAssignments(op, rewriter);
}

//===----------------------------------------------------------------------===//
// TypeDecl operations
//===----------------------------------------------------------------------===//
Expand Down
32 changes: 32 additions & 0 deletions test/Dialect/SV/canonicalization.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,35 @@ hw.module @case_stmt(%arg: i3) {
}

}

// CHECK-LABEL: MergeAssignments
hw.module @MergeAssignments(%a: !hw.array<4xi1>, %clock: i1) -> (d: !hw.array<4xi1>) {
%c-1_i2 = hw.constant -1 : i2
%c-2_i2 = hw.constant -2 : i2
%c1_i2 = hw.constant 1 : i2
%c0_i2 = hw.constant 0 : i2
%v0 = hw.array_get %a[%c0_i2] : !hw.array<4xi1>, i2
%v1 = hw.array_get %a[%c1_i2] : !hw.array<4xi1>, i2
%v2 = hw.array_get %a[%c-2_i2] : !hw.array<4xi1>, i2
%r = sv.reg : !hw.inout<array<4xi1>>
%i0 = sv.array_index_inout %r[%c0_i2] : !hw.inout<array<4xi1>>, i2
%i1 = sv.array_index_inout %r[%c1_i2] : !hw.inout<array<4xi1>>, i2
%i2 = sv.array_index_inout %r[%c-2_i2] : !hw.inout<array<4xi1>>, i2
%read = sv.read_inout %r : !hw.inout<array<4xi1>>
sv.always posedge %clock {
// CHECK: sv.always
// CHECK-NEXT: %[[index:.+]] = sv.indexed_part_select_inout %r[%c0_i2 : 3] : !hw.inout<array<4xi1>>, i2
// CHECK-NEXT: %[[slice:.+]] = hw.array_slice %a[%c0_i2] : (!hw.array<4xi1>) -> !hw.array<3xi1>
// CHECK-NEXT: sv.passign %[[index]], %[[slice]]
// CHECK-NEXT: %[[index:.+]] = sv.indexed_part_select_inout %r[%c0_i2 : 3] : !hw.inout<array<4xi1>>, i2
// CHECK-NEXT: %[[slice:.+]] = hw.array_slice %a[%c0_i2] : (!hw.array<4xi1>) -> !hw.array<3xi1>
// CHECK-NEXT: sv.bpassign %[[index]], %[[slice]]
sv.passign %i0, %v0 : i1
sv.passign %i1, %v1 : i1
sv.passign %i2, %v2 : i1
sv.bpassign %i0, %v0 : i1
sv.bpassign %i1, %v1 : i1
sv.bpassign %i2, %v2 : i1
}
hw.output %read : !hw.array<4xi1>
}

0 comments on commit e477486

Please sign in to comment.