[MooreToCore] Fix variable op lowering of aggregate types (#7481) (#7362)

This commit is contained in:
Martin Erhart 2024-08-09 09:58:03 +01:00 committed by GitHub
parent 84d73b94aa
commit 273439ec61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 286 additions and 11 deletions

View File

@ -516,7 +516,7 @@ def ConvertMooreToCore : Pass<"convert-moore-to-core", "mlir::ModuleOp"> {
}];
let constructor = "circt::createConvertMooreToCorePass()";
let dependentDialects = ["comb::CombDialect", "hw::HWDialect",
"llhd::LLHDDialect"];
"llhd::LLHDDialect", "mlir::cf::ControlFlowDialect"];
}
//===----------------------------------------------------------------------===//

View File

@ -15,4 +15,5 @@ add_circt_conversion_library(CIRCTMooreToCore
MLIRControlFlowDialect
MLIRFuncDialect
MLIRTransforms
MLIRSideEffectInterfaces
)

View File

@ -18,6 +18,7 @@
#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/IR/BuiltinDialect.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/DialectConversion.h"
#include "llvm/ADT/TypeSwitch.h"
@ -148,6 +149,152 @@ struct InstanceOpConversion : public OpConversionPattern<InstanceOp> {
}
};
struct ProcedureOpConversion : public OpConversionPattern<ProcedureOp> {
using OpConversionPattern::OpConversionPattern;
LogicalResult
matchAndRewrite(ProcedureOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Location loc = op.getLoc();
auto procOp = rewriter.create<llhd::ProcessOp>(loc);
// TODO: properly handle the procedure kind attribute
if (op.getKind() != ProcedureKind::Always)
return rewriter.notifyMatchFailure(loc, "not yet supported");
// Collect all event ops in the procedure.
SmallVector<EventOp> events(op.getOps<EventOp>());
auto *entry = rewriter.createBlock(&procOp.getBody());
auto *wait = rewriter.createBlock(&procOp.getBody());
auto *check = rewriter.createBlock(&procOp.getBody());
// We need to add an empty entry block because it is not allowed in MLIR to
// branch back to the entry block. Instead we put the logic in the second
// block and branch to that.
rewriter.setInsertionPointToStart(entry);
rewriter.create<cf::BranchOp>(loc, wait);
// The block in which we can sample the past and where the wait terminator
// resides.
rewriter.setInsertionPointToStart(wait);
auto getSignal = [&](Value input) -> Value {
// If the read op input is defined outside and before the procedure
// operation, we can get the remapped value directly.
Value signal = rewriter.getRemappedValue(input);
// Otherwise, it hasn't been converted yet, so we take the old one and
// insert a cast.
if (!signal) {
Type convertedType = typeConverter->convertType(input.getType());
assert(convertedType &&
"if the input has not been converted yet, it should have a "
"moore type and a valid type conversion");
signal =
rewriter
.create<UnrealizedConversionCastOp>(loc, convertedType, input)
->getResult(0);
}
return signal;
};
// All signals to observe in the `llhd.wait` operation.
SmallVector<Value> toObserve;
DenseSet<Value> alreadyObserved;
// If there are no event operations in the procedure, it's a combinational
// one. Thus we need to collect all signals used.
if (events.empty()) {
op->walk([&](Operation *operation) {
for (auto &operand : operation->getOpOperands()) {
Value value = getSignal(operand.get());
auto memOp = dyn_cast<MemoryEffectOpInterface>(operation);
if (!memOp)
return;
// The process is only sensitive to values that are read.
if (isa<RefType>(operand.get().getType()) &&
memOp.getEffectOnValue<MemoryEffects::Read>(operand.get())
.has_value()) {
if (!alreadyObserved.contains(value))
toObserve.push_back(value);
alreadyObserved.insert(value);
}
}
});
}
// Forall edge triggered events, probe the old value
SmallVector<Value> oldValues(events.size(), Value());
for (auto [i, event] : llvm::enumerate(events)) {
auto readOp = event.getInput().getDefiningOp<ReadOp>();
if (!readOp)
return failure();
Value signal = getSignal(readOp.getInput());
toObserve.push_back(signal);
// Non-edge triggered events only need the value in the present
if (event.getEdge() != Edge::None)
oldValues[i] = rewriter.create<llhd::PrbOp>(loc, signal);
}
rewriter.create<llhd::WaitOp>(loc, toObserve, Value(), ValueRange{}, check);
rewriter.setInsertionPointToStart(check);
if (events.empty()) {
rewriter.create<cf::BranchOp>(loc, &op.getBody().front());
} else {
SmallVector<Value> disjuncts;
for (auto [i, signal, event] : llvm::enumerate(toObserve, events)) {
if (event.getEdge() == Edge::None)
disjuncts.push_back(rewriter.create<llhd::PrbOp>(loc, signal));
if (event.getEdge() == Edge::PosEdge ||
event.getEdge() == Edge::BothEdges) {
Value currVal = rewriter.create<llhd::PrbOp>(loc, signal);
Value trueVal = rewriter.create<hw::ConstantOp>(loc, APInt(1, 1));
Value notOldVal =
rewriter.create<comb::XorOp>(loc, oldValues[i], trueVal);
Value posedge = rewriter.create<comb::AndOp>(loc, notOldVal, currVal);
disjuncts.push_back(posedge);
}
if (event.getEdge() == Edge::NegEdge ||
event.getEdge() == Edge::BothEdges) {
Value currVal = rewriter.create<llhd::PrbOp>(loc, signal);
Value trueVal = rewriter.create<hw::ConstantOp>(loc, APInt(1, 1));
Value notCurrVal =
rewriter.create<comb::XorOp>(loc, currVal, trueVal);
Value posedge =
rewriter.create<comb::AndOp>(loc, oldValues[i], notCurrVal);
disjuncts.push_back(posedge);
}
}
Value isValid = rewriter.create<comb::OrOp>(loc, disjuncts, false);
rewriter.create<cf::CondBranchOp>(loc, isValid, &op.getBody().front(),
wait);
}
for (auto event : events)
rewriter.eraseOp(event);
rewriter.inlineRegionBefore(op.getBody(), procOp.getBody(),
procOp.getBody().end());
for (auto returnOp : procOp.getOps<ReturnOp>()) {
rewriter.setInsertionPoint(returnOp);
rewriter.create<cf::BranchOp>(loc, wait);
rewriter.eraseOp(returnOp);
}
rewriter.eraseOp(op);
return success();
}
};
//===----------------------------------------------------------------------===//
// Declaration Conversion
//===----------------------------------------------------------------------===//
@ -617,14 +764,12 @@ struct ReadOpConversion : public OpConversionPattern<ReadOp> {
LogicalResult
matchAndRewrite(ReadOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
Type resultType = typeConverter->convertType(op.getResult().getType());
rewriter.replaceOpWithNewOp<llhd::PrbOp>(op, resultType,
adaptor.getInput());
rewriter.replaceOpWithNewOp<llhd::PrbOp>(op, adaptor.getInput());
return success();
}
};
template <typename OpTy>
template <typename OpTy, unsigned DeltaTime, unsigned EpsilonTime>
struct AssignOpConversion : public OpConversionPattern<OpTy> {
using OpConversionPattern<OpTy>::OpConversionPattern;
using OpAdaptor = typename OpTy::Adaptor;
@ -634,9 +779,8 @@ struct AssignOpConversion : public OpConversionPattern<OpTy> {
ConversionPatternRewriter &rewriter) const override {
// TODO: When we support delay control in Moore dialect, we need to update
// this conversion.
auto timeAttr =
llhd::TimeAttr::get(op->getContext(), unsigned(0),
llvm::StringRef("ns"), unsigned(0), unsigned(0));
auto timeAttr = llhd::TimeAttr::get(
op->getContext(), 0U, llvm::StringRef("ns"), DeltaTime, EpsilonTime);
auto time = rewriter.create<llhd::ConstantTimeOp>(op->getLoc(), timeAttr);
rewriter.replaceOpWithNewOp<llhd::DrvOp>(op, adaptor.getDst(),
adaptor.getSrc(), time, Value{});
@ -794,13 +938,15 @@ static void populateOpConversion(RewritePatternSet &patterns,
ICmpOpConversion<WildcardNeOp, ICmpPredicate::wne>,
// Patterns of structural operations.
SVModuleOpConversion, InstanceOpConversion,
SVModuleOpConversion, InstanceOpConversion, ProcedureOpConversion,
// Patterns of shifting operations.
ShrOpConversion, ShlOpConversion, AShrOpConversion,
// Patterns of assignment operations.
AssignOpConversion<ContinuousAssignOp>,
AssignOpConversion<ContinuousAssignOp, 0, 1>,
AssignOpConversion<BlockingAssignOp, 0, 1>,
AssignOpConversion<NonBlockingAssignOp, 1, 0>,
// Patterns of branch operations.
CondBranchOpConversion, BranchOpConversion,

View File

@ -304,7 +304,7 @@ moore.module @Variable() {
// CHECK: [[TMP2:%.+]] = hw.constant 10 : i32
%3 = moore.constant 10 : i32
// CHECK: [[TIME:%.+]] = llhd.constant_time <0ns, 0d, 0e>
// CHECK: [[TIME:%.+]] = llhd.constant_time <0ns, 0d, 1e>
// CHECK: llhd.drv [[A]], [[TMP2]] after [[TIME]] : !hw.inout<i32>
moore.assign %a, %3 : i32
@ -329,3 +329,131 @@ moore.module @Struct(in %arg0 : !moore.struct<{exp_bits: i32, man_bits: i32}>, o
moore.output %0, %3, %4 : !moore.i32, !moore.struct<{exp_bits: i32, man_bits: i32}>, !moore.struct<{exp_bits: i32, man_bits: i32}>
}
// CHECK-LABEL: hw.module @Process
moore.module @Process(in %cond : i1) {
// CHECK: [[B:%.+]] = llhd.sig "b"
// CHECK: [[C:%.+]] = llhd.sig "c"
// CHECK: [[D:%.+]] = llhd.sig "d"
// CHECK: [[E:%.+]] = llhd.sig "e"
%b = moore.variable : <i1>
%c = moore.variable : <i1>
%d = moore.variable : <i1>
%e = moore.variable : <i1>
// CHECK: llhd.process
moore.procedure always {
// CHECK: cf.br ^[[BB1:.+]]
// CHECK: ^[[BB1]]:
// CHECK: [[C_OLD:%.+]] = llhd.prb [[C]]
// CHECK: llhd.wait ([[B]], [[C]] : !hw.inout<i1>, !hw.inout<i1>), ^[[BB2:.+]]
// CHECK: ^[[BB2]]:
// CHECK: [[B_CURR:%.+]] = llhd.prb [[B]]
// CHECK: [[C_CURR:%.+]] = llhd.prb [[C]]
// CHECK: [[NOT_C_OLD:%.+]] = comb.xor [[C_OLD]], %true
// CHECK: [[POSEDGE:%.+]] = comb.and [[NOT_C_OLD]], [[C_CURR]] : i1
// CHECK: [[PROCEED:%.+]] = comb.or [[B_CURR]], [[POSEDGE]] : i1
// CHECK: cf.cond_br [[PROCEED]], ^[[BB3:.+]], ^[[BB1]]
// CHECK: ^[[BB3]]:
// CHECK: [[B_PRB:%.+]] = llhd.prb [[B]]
// CHECK: [[C_PRB:%.+]] = llhd.prb [[C]]
// CHECK: [[RES:%.+]] = comb.add [[B_PRB]], [[C_PRB]] : i1
// CHECK: [[T0:%.+]] = llhd.constant_time <0ns, 1d, 0e>
// CHECK: llhd.drv [[D]], [[RES]] after [[T0]]
// CHECK: [[T1:%.+]] = llhd.constant_time <0ns, 0d, 1e>
// CHECK: llhd.drv [[E]], [[RES]] after [[T1]]
// CHECK: cf.br ^[[BB1]]
%br = moore.read %b : <i1>
moore.wait_event none %br : i1
%cr = moore.read %c : <i1>
moore.wait_event posedge %cr : i1
%0 = moore.add %br, %cr : i1
moore.nonblocking_assign %d, %0 : i1
moore.blocking_assign %e, %0 : i1
moore.return
}
// CHECK: llhd.process
moore.procedure always {
// CHECK: cf.br ^[[BB1:.+]]
// CHECK: ^[[BB1]]:
// CHECK: llhd.wait ([[B]], [[C]] : !hw.inout<i1>, !hw.inout<i1>), ^[[BB2:.+]]
// CHECK: ^[[BB2]]:
// CHECK: cf.br ^[[BB3:.+]]
// CHECK: ^[[BB3]]:
// CHECK: [[B_PRB:%.+]] = llhd.prb [[B]]
// CHECK: [[C_PRB:%.+]] = llhd.prb [[C]]
// CHECK: [[RES:%.+]] = comb.add [[B_PRB]], [[C_PRB]] : i1
// CHECK: [[T0:%.+]] = llhd.constant_time <0ns, 1d, 0e>
// CHECK: llhd.drv [[D]], [[RES]] after [[T0]]
// CHECK: cf.br ^[[BB1]]
%br = moore.read %b : <i1>
%cr = moore.read %c : <i1>
%0 = moore.add %br, %cr : i1
moore.nonblocking_assign %d, %0 : i1
moore.return
}
// CHECK: llhd.process
moore.procedure always {
// CHECK: cf.br ^[[BB1:.+]]
// CHECK: ^[[BB1]]:
// CHECK: [[C_OLD:%.+]] = llhd.prb [[C]]
// CHECK: llhd.wait ([[C]] : !hw.inout<i1>), ^[[BB2:.+]]
// CHECK: ^[[BB2]]:
// CHECK: [[C_CURR:%.+]] = llhd.prb [[C]]
// CHECK: [[NOT_C_OLD:%.+]] = comb.xor [[C_OLD]], %true
// CHECK: [[POSEDGE:%.+]] = comb.and [[NOT_C_OLD]], [[C_CURR]] : i1
// CHECK: [[C_CURR1:%.+]] = llhd.prb [[C]]
// CHECK: [[NOT_C_CURR:%.+]] = comb.xor [[C_CURR1]], %true
// CHECK: [[NEGEDGE:%.+]] = comb.and [[C_OLD]], [[NOT_C_CURR]] : i1
// CHECK: [[PROCEED:%.+]] = comb.or [[POSEDGE]], [[NEGEDGE]] : i1
// CHECK: cf.cond_br [[PROCEED]], ^[[BB3:.+]], ^[[BB1]]
// CHECK: ^[[BB3]]:
// CHECK: [[RES:%.+]] = comb.add [[B_PRB:%.+]], [[C_PRB:%.+]] : i1
// CHECK: [[T0:%.+]] = llhd.constant_time <0ns, 1d, 0e>
// CHECK: llhd.drv [[D]], [[RES]] after [[T0]]
// CHECK: cf.br ^[[BB1]]
moore.wait_event edge %cr : i1
%0 = moore.add %br, %cr : i1
moore.nonblocking_assign %d, %0 : i1
moore.return
}
// CHECK: [[B_PRB]] = llhd.prb [[B]]
// CHECK: [[C_PRB]] = llhd.prb [[C]]
%br = moore.read %b : <i1>
%cr = moore.read %c : <i1>
// CHECK: llhd.process
moore.procedure always {
// CHECK: cf.br ^[[BB1:.+]]
// CHECK: ^[[BB1]]:
// CHECK: [[C_OLD:%.+]] = llhd.prb [[C]]
// CHECK: llhd.wait ([[C]] : !hw.inout<i1>), ^[[BB2:.+]]
// CHECK: ^[[BB2]]:
// CHECK: [[C_CURR:%.+]] = llhd.prb [[C]]
// CHECK: [[NOT_C_CURR:%.+]] = comb.xor [[C_CURR]], %true
// CHECK: [[NEGEDGE:%.+]] = comb.and [[C_OLD]], [[NOT_C_CURR]] : i1
// CHECK: [[PROCEED:%.+]] = comb.or [[NEGEDGE]] : i1
// CHECK: cf.cond_br [[PROCEED]], ^[[BB3:.+]], ^[[BB1]]
// CHECK: ^[[BB3]]:
// CHECK: [[RES:%.+]] = comb.add [[B_PRB]], [[C_PRB]] : i1
// CHECK: cf.cond_br %cond, ^[[BB4:.+]], ^[[BB5:.+]]
// CHECK: ^[[BB4]]:
// CHECK: [[T0:%.+]] = llhd.constant_time <0ns, 1d, 0e>
// CHECK: llhd.drv [[D]], [[RES]] after [[T0]]
// CHECK: cf.br ^[[BB1]]
// CHECK: ^[[BB5]]:
// CHECK: cf.br ^[[BB1]]
moore.wait_event negedge %cr : i1
%0 = moore.add %br, %cr : i1
cf.cond_br %cond, ^bb1, ^bb2
^bb1:
moore.nonblocking_assign %d, %0 : i1
moore.return
^bb2:
moore.return
}
}