Skip to content
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

[MooreToCore] Add always_comb and always_latch lowering support #7532

Merged
merged 1 commit into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 63 additions & 59 deletions lib/Conversion/MooreToCore/MooreToCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/SCF/IR/SCF.h"
#include "mlir/IR/BuiltinDialect.h"
#include "mlir/IR/Iterators.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/DialectConversion.h"
#include "mlir/Transforms/RegionUtils.h"
#include "llvm/ADT/TypeSwitch.h"

namespace circt {
Expand Down Expand Up @@ -103,30 +105,6 @@ static hw::ModulePortInfo getModulePortInfo(const TypeConverter &typeConverter,
return hw::ModulePortInfo(inputs, outputs);
}

/// Erase all dead blocks in a region.
static void eraseDeadBlocks(PatternRewriter &rewriter, Region &region) {
SmallVector<Block *> worklist;
for (auto &block : llvm::make_early_inc_range(llvm::drop_begin(region, 1))) {
if (!block.use_empty())
continue;
worklist.push_back(&block);
while (!worklist.empty()) {
auto *block = worklist.pop_back_val();
if (!block->use_empty())
continue;
for (auto *successor : block->getSuccessors())
worklist.push_back(successor);
rewriter.eraseBlock(block);
}
}
}

/// Erase all dead blocks in an op.
static void eraseDeadBlocks(PatternRewriter &rewriter, Operation *op) {
for (auto &region : op->getRegions())
eraseDeadBlocks(rewriter, region);
}

//===----------------------------------------------------------------------===//
// Structural Conversion
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -188,16 +166,63 @@ struct InstanceOpConversion : public OpConversionPattern<InstanceOp> {
}
};

static void getValuesToObserve(Region *region,
function_ref<void(Value)> setInsertionPoint,
const TypeConverter *typeConverter,
ConversionPatternRewriter &rewriter,
SmallVector<Value> &observeValues) {
SmallDenseSet<Value> alreadyObserved;
Location loc = region->getLoc();

auto probeIfSignal = [&](Value value) -> Value {
if (!isa<hw::InOutType>(value.getType()))
return value;
return rewriter.create<llhd::PrbOp>(loc, value);
};

region->getParentOp()->walk<WalkOrder::PreOrder, ForwardDominanceIterator<>>(
[&](Operation *operation) {
for (auto value : operation->getOperands()) {
if (region->isAncestor(value.getParentRegion()))
continue;
if (!alreadyObserved.insert(value).second)
continue;

OpBuilder::InsertionGuard g(rewriter);
if (auto remapped = rewriter.getRemappedValue(value)) {
setInsertionPoint(remapped);
observeValues.push_back(probeIfSignal(remapped));
} else {
setInsertionPoint(value);
auto type = typeConverter->convertType(value.getType());
auto converted = typeConverter->materializeTargetConversion(
rewriter, loc, type, value);
observeValues.push_back(probeIfSignal(converted));
}
}
});
}

struct ProcedureOpConversion : public OpConversionPattern<ProcedureOp> {
using OpConversionPattern::OpConversionPattern;

LogicalResult
matchAndRewrite(ProcedureOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
// Collect values to observe before we do any modifications to the region.
SmallVector<Value> observedValues;
if (op.getKind() == ProcedureKind::AlwaysComb ||
op.getKind() == ProcedureKind::AlwaysLatch) {
auto setInsertionPoint = [&](Value value) {
rewriter.setInsertionPoint(op);
};
getValuesToObserve(&op.getBody(), setInsertionPoint, typeConverter,
rewriter, observedValues);
}

auto loc = op.getLoc();
if (failed(rewriter.convertRegionTypes(&op.getBody(), *typeConverter)))
return failure();
eraseDeadBlocks(rewriter, op);

// Handle initial and final procedures. These lower to a corresponding
// `llhd.process` or `llhd.final` op that executes the body and then halts.
Expand Down Expand Up @@ -239,9 +264,10 @@ struct ProcedureOpConversion : public OpConversionPattern<ProcedureOp> {
// jumping back up to the body.
if (op.getKind() == ProcedureKind::AlwaysComb ||
op.getKind() == ProcedureKind::AlwaysLatch) {
block = rewriter.createBlock(&newOp.getBody());
// TODO: Collect observed signals and add LLHD wait op.
return failure();
Block *waitBlock = rewriter.createBlock(&newOp.getBody());
rewriter.create<llhd::WaitOp>(loc, observedValues, Value(), ValueRange{},
block);
block = waitBlock;
}

// Make all `moore.return` ops branch back up to the beginning of the
Expand Down Expand Up @@ -322,44 +348,19 @@ struct WaitEventOpConversion : public OpConversionPattern<WaitEventOp> {
rewriter.eraseOp(detectOp);
}

// Determine the values used during event detection that are defined outside
// the `wait_event`'s body region. We want to wait for a change on these
// signals before we check if any interesting event happened.
SmallVector<Value> observeValues;
auto setInsertionPointAfterDef = [&](Value value) {
if (auto *op = value.getDefiningOp())
rewriter.setInsertionPointAfter(op);
if (auto arg = dyn_cast<BlockArgument>(value))
rewriter.setInsertionPointToStart(value.getParentBlock());
};

auto probeIfSignal = [&](Value value) -> Value {
if (!isa<hw::InOutType>(value.getType()))
return value;
return rewriter.create<llhd::PrbOp>(loc, value);
};

// Determine the values used during event detection that are defined outside
// the `wait_event`'s body region. We want to wait for a change on these
// signals before we check if any interesting event happened.
SmallVector<Value> observeValues;
SmallDenseSet<Value> alreadyObserved;
clonedOp.getBody().walk([&](Operation *operation) {
for (auto value : operation->getOperands()) {
if (clonedOp.getBody().isAncestor(value.getParentRegion()))
continue;
if (!alreadyObserved.insert(value).second)
continue;
if (auto remapped = rewriter.getRemappedValue(value)) {
OpBuilder::InsertionGuard g(rewriter);
setInsertionPointAfterDef(remapped);
observeValues.push_back(probeIfSignal(remapped));
} else {
OpBuilder::InsertionGuard g(rewriter);
setInsertionPointAfterDef(value);
auto type = typeConverter->convertType(value.getType());
auto converted = typeConverter->materializeTargetConversion(
rewriter, loc, type, value);
observeValues.push_back(probeIfSignal(converted));
}
}
});
getValuesToObserve(&clonedOp.getBody(), setInsertionPointAfterDef,
typeConverter, rewriter, observeValues);

// Create the `llhd.wait` op that suspends the current process and waits for
// a change in the interesting values listed in `observeValues`. When a
Expand Down Expand Up @@ -1388,6 +1389,9 @@ void MooreToCorePass::runOnOperation() {
MLIRContext &context = getContext();
ModuleOp module = getOperation();

IRRewriter rewriter(module);
(void)mlir::eraseUnreachableBlocks(rewriter, module->getRegions());

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very neat!

ConversionTarget target(context);
TypeConverter typeConverter;
RewritePatternSet patterns(&context);
Expand Down
74 changes: 74 additions & 0 deletions test/Conversion/MooreToCore/basic.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,45 @@ moore.module @WaitEvent() {
moore.return
}

// CHECK: [[FALSE:%.+]] = hw.constant false
// CHECK: [[PRB_A:%.+]] = llhd.prb %a
// CHECK: [[PRB_B:%.+]] = llhd.prb %b
// CHECK: llhd.process {
%cond = moore.constant 0 : i1
moore.procedure always_comb {
// CHECK: cf.br ^[[BB1:.+]]
// CHECK: ^[[BB1]]:
// CHECK: llhd.prb %a
// CHECK: llhd.prb %b
// CHECK: cf.br ^[[BB2:.+]]
// CHECK: ^[[BB2]]:
// CHECK: llhd.wait ([[FALSE]], [[PRB_A]], [[PRB_B]] : {{.*}}), ^[[BB1]]
%1 = moore.conditional %cond : i1 -> i1 {
%2 = moore.read %a : <i1>
moore.yield %2 : !moore.i1
} {
%3 = moore.read %b : <i1>
moore.yield %3 : !moore.i1
}
moore.return
}

// CHECK: [[PRB_D:%.+]] = llhd.prb %d
// CHECK: llhd.process {
// CHECK: cf.br ^[[BB1:.+]]
// CHECK: ^[[BB1]]:
// CHECK: llhd.prb %d
// CHECK: cf.br ^[[BB2:.+]]
// CHECK: ^[[BB2]]:
// CHECK: llhd.wait ([[PRB_D]] : {{.*}}), ^[[BB1]]
moore.procedure always_latch {
%3 = moore.read %d : <i1>
moore.return
}

// CHECK: %d = llhd.sig %false
%d = moore.variable : <i1>

// CHECK: llhd.process {
moore.procedure initial {
// CHECK: llhd.wait ([[PRB_A6]], [[PRB_B2]] :
Expand All @@ -708,3 +747,38 @@ moore.module @WaitEvent() {
moore.return
}
}

// Just check that block without predecessors are handled without crashing
// CHECK-LABEL: @NoPredecessorBlockErasure
moore.module @NoPredecessorBlockErasure(in %clk_i : !moore.l1, in %raddr_i : !moore.array<2 x l5>, out rdata_o : !moore.array<2 x l32>, in %waddr_i : !moore.array<1 x l5>, in %wdata_i : !moore.array<1 x l32>, in %we_i : !moore.l1) {
%0 = moore.constant 0 : l32
%1 = moore.constant 1 : i32
%2 = moore.constant 0 : i32
%rdata_o = moore.variable : <array<2 x l32>>
%mem = moore.variable : <array<32 x l32>>
moore.procedure always_ff {
cf.br ^bb1(%2 : !moore.i32)
^bb1(%4: !moore.i32): // 2 preds: ^bb0, ^bb8
moore.return
^bb2: // no predecessors
cf.br ^bb3(%2 : !moore.i32)
^bb3(%5: !moore.i32): // 2 preds: ^bb2, ^bb6
cf.br ^bb8
^bb4: // no predecessors
cf.br ^bb6
^bb5: // no predecessors
cf.br ^bb6
^bb6: // 2 preds: ^bb4, ^bb5
%6 = moore.add %5, %1 : i32
cf.br ^bb3(%6 : !moore.i32)
^bb7: // no predecessors
%7 = moore.extract_ref %mem from 0 : <array<32 x l32>> -> <l32>
moore.nonblocking_assign %7, %0 : l32
cf.br ^bb8
^bb8: // 2 preds: ^bb3, ^bb7
%8 = moore.add %4, %1 : i32
cf.br ^bb1(%8 : !moore.i32)
}
%3 = moore.read %rdata_o : <array<2 x l32>>
moore.output %3 : !moore.array<2 x l32>
}
Loading