From 273439ec61037fbd963eadb3094322adeda0e1c5 Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Fri, 9 Aug 2024 09:58:03 +0100 Subject: [PATCH] [MooreToCore] Fix variable op lowering of aggregate types (#7481) (#7362) --- include/circt/Conversion/Passes.td | 2 +- lib/Conversion/MooreToCore/CMakeLists.txt | 1 + lib/Conversion/MooreToCore/MooreToCore.cpp | 164 +++++++++++++++++++-- test/Conversion/MooreToCore/basic.mlir | 130 +++++++++++++++- 4 files changed, 286 insertions(+), 11 deletions(-) diff --git a/include/circt/Conversion/Passes.td b/include/circt/Conversion/Passes.td index 684e6616cb..7cf405fefe 100644 --- a/include/circt/Conversion/Passes.td +++ b/include/circt/Conversion/Passes.td @@ -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"]; } //===----------------------------------------------------------------------===// diff --git a/lib/Conversion/MooreToCore/CMakeLists.txt b/lib/Conversion/MooreToCore/CMakeLists.txt index d97747ce84..89e706ea9d 100644 --- a/lib/Conversion/MooreToCore/CMakeLists.txt +++ b/lib/Conversion/MooreToCore/CMakeLists.txt @@ -15,4 +15,5 @@ add_circt_conversion_library(CIRCTMooreToCore MLIRControlFlowDialect MLIRFuncDialect MLIRTransforms + MLIRSideEffectInterfaces ) diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index f6b53d77d7..af58a7e1ed 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -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 { } }; +struct ProcedureOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(ProcedureOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Location loc = op.getLoc(); + auto procOp = rewriter.create(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 events(op.getOps()); + + 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(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(loc, convertedType, input) + ->getResult(0); + } + + return signal; + }; + + // All signals to observe in the `llhd.wait` operation. + SmallVector toObserve; + DenseSet 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(operation); + if (!memOp) + return; + + // The process is only sensitive to values that are read. + if (isa(operand.get().getType()) && + memOp.getEffectOnValue(operand.get()) + .has_value()) { + if (!alreadyObserved.contains(value)) + toObserve.push_back(value); + + alreadyObserved.insert(value); + } + } + }); + } + + // Forall edge triggered events, probe the old value + SmallVector oldValues(events.size(), Value()); + for (auto [i, event] : llvm::enumerate(events)) { + auto readOp = event.getInput().getDefiningOp(); + 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(loc, signal); + } + + rewriter.create(loc, toObserve, Value(), ValueRange{}, check); + rewriter.setInsertionPointToStart(check); + + if (events.empty()) { + rewriter.create(loc, &op.getBody().front()); + } else { + SmallVector disjuncts; + for (auto [i, signal, event] : llvm::enumerate(toObserve, events)) { + if (event.getEdge() == Edge::None) + disjuncts.push_back(rewriter.create(loc, signal)); + + if (event.getEdge() == Edge::PosEdge || + event.getEdge() == Edge::BothEdges) { + Value currVal = rewriter.create(loc, signal); + Value trueVal = rewriter.create(loc, APInt(1, 1)); + Value notOldVal = + rewriter.create(loc, oldValues[i], trueVal); + Value posedge = rewriter.create(loc, notOldVal, currVal); + disjuncts.push_back(posedge); + } + if (event.getEdge() == Edge::NegEdge || + event.getEdge() == Edge::BothEdges) { + Value currVal = rewriter.create(loc, signal); + Value trueVal = rewriter.create(loc, APInt(1, 1)); + Value notCurrVal = + rewriter.create(loc, currVal, trueVal); + Value posedge = + rewriter.create(loc, oldValues[i], notCurrVal); + disjuncts.push_back(posedge); + } + } + + Value isValid = rewriter.create(loc, disjuncts, false); + rewriter.create(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()) { + rewriter.setInsertionPoint(returnOp); + rewriter.create(loc, wait); + rewriter.eraseOp(returnOp); + } + + rewriter.eraseOp(op); + return success(); + } +}; + //===----------------------------------------------------------------------===// // Declaration Conversion //===----------------------------------------------------------------------===// @@ -617,14 +764,12 @@ struct ReadOpConversion : public OpConversionPattern { LogicalResult matchAndRewrite(ReadOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - Type resultType = typeConverter->convertType(op.getResult().getType()); - rewriter.replaceOpWithNewOp(op, resultType, - adaptor.getInput()); + rewriter.replaceOpWithNewOp(op, adaptor.getInput()); return success(); } }; -template +template struct AssignOpConversion : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; using OpAdaptor = typename OpTy::Adaptor; @@ -634,9 +779,8 @@ struct AssignOpConversion : public OpConversionPattern { 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(op->getLoc(), timeAttr); rewriter.replaceOpWithNewOp(op, adaptor.getDst(), adaptor.getSrc(), time, Value{}); @@ -794,13 +938,15 @@ static void populateOpConversion(RewritePatternSet &patterns, ICmpOpConversion, // Patterns of structural operations. - SVModuleOpConversion, InstanceOpConversion, + SVModuleOpConversion, InstanceOpConversion, ProcedureOpConversion, // Patterns of shifting operations. ShrOpConversion, ShlOpConversion, AShrOpConversion, // Patterns of assignment operations. - AssignOpConversion, + AssignOpConversion, + AssignOpConversion, + AssignOpConversion, // Patterns of branch operations. CondBranchOpConversion, BranchOpConversion, diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index 1df63b4ea9..64c105f1d2 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -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 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 : + %c = moore.variable : + %d = moore.variable : + %e = moore.variable : + + // 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, !hw.inout), ^[[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 : + moore.wait_event none %br : i1 + %cr = moore.read %c : + 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, !hw.inout), ^[[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 : + %cr = moore.read %c : + %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), ^[[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 : + %cr = moore.read %c : + // CHECK: llhd.process + moore.procedure always { + // CHECK: cf.br ^[[BB1:.+]] + // CHECK: ^[[BB1]]: + // CHECK: [[C_OLD:%.+]] = llhd.prb [[C]] + // CHECK: llhd.wait ([[C]] : !hw.inout), ^[[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 + } +}