diff --git a/include/circt/Dialect/Arc/ArcOps.td b/include/circt/Dialect/Arc/ArcOps.td index 08a3e37eca20..0842d66b6cda 100644 --- a/include/circt/Dialect/Arc/ArcOps.td +++ b/include/circt/Dialect/Arc/ArcOps.td @@ -466,7 +466,14 @@ def PassThroughOp : ClockTreeLikeOp<"passthrough"> { } def InitialOp : ClockTreeLikeOp<"initial"> { - let summary = "Clock-less logic called at the start of simulation"; + let summary = "Region to be executed at the start of simulation"; + let assemblyFormat = [{ + attr-dict-with-keyword $body + }]; +} + +def FinalOp : ClockTreeLikeOp<"final"> { + let summary = "Region to be executed at the end of simulation"; let assemblyFormat = [{ attr-dict-with-keyword $body }]; @@ -672,11 +679,15 @@ def ModelOp : ArcOp<"model", [ }]; let arguments = (ins SymbolNameAttr:$sym_name, TypeAttrOf:$io, - OptionalAttr:$initialFn); + OptionalAttr:$initialFn, + OptionalAttr:$finalFn); let regions = (region SizedRegion<1>:$body); let assemblyFormat = [{ - $sym_name `io` $io (`initializer` $initialFn^)? attr-dict-with-keyword $body + $sym_name `io` $io + (`initializer` $initialFn^)? + (`finalizer` $finalFn^)? + attr-dict-with-keyword $body }]; let extraClassDeclaration = [{ diff --git a/include/circt/Dialect/Arc/ModelInfo.h b/include/circt/Dialect/Arc/ModelInfo.h index ca3918a772f0..6da58d10a852 100644 --- a/include/circt/Dialect/Arc/ModelInfo.h +++ b/include/circt/Dialect/Arc/ModelInfo.h @@ -37,12 +37,15 @@ struct ModelInfo { size_t numStateBytes; llvm::SmallVector states; mlir::FlatSymbolRefAttr initialFnSym; + mlir::FlatSymbolRefAttr finalFnSym; ModelInfo(std::string name, size_t numStateBytes, llvm::SmallVector states, - mlir::FlatSymbolRefAttr initialFnSym) + mlir::FlatSymbolRefAttr initialFnSym, + mlir::FlatSymbolRefAttr finalFnSym) : name(std::move(name)), numStateBytes(numStateBytes), - states(std::move(states)), initialFnSym(initialFnSym) {} + states(std::move(states)), initialFnSym(initialFnSym), + finalFnSym(finalFnSym) {} }; /// Collects information about states within the provided Arc model storage diff --git a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp index 8f4ead1fe604..63ad9b0375a3 100644 --- a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp +++ b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp @@ -320,6 +320,7 @@ struct ModelInfoMap { size_t numStateBytes; llvm::DenseMap states; mlir::FlatSymbolRefAttr initialFnSymbol; + mlir::FlatSymbolRefAttr finalFnSymbol; }; template @@ -390,8 +391,19 @@ struct SimInstantiateOpLowering ValueRange{allocated}); } + // Execute the body. rewriter.inlineBlockBefore(&adaptor.getBody().getBlocks().front(), op, {allocated}); + + // Call the model's 'final' function if present. + if (model.finalFnSymbol) { + auto finalFnType = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(op.getContext()), + {LLVM::LLVMPointerType::get(op.getContext())}); + rewriter.create(loc, finalFnType, model.finalFnSymbol, + ValueRange{allocated}); + } + rewriter.create(loc, freeFunc, ValueRange{allocated}); rewriter.eraseOp(op); @@ -657,9 +669,10 @@ void LowerArcToLLVMPass::runOnOperation() { llvm::DenseMap states(modelInfo.states.size()); for (StateInfo &stateInfo : modelInfo.states) states.insert({stateInfo.name, stateInfo}); - modelMap.insert({modelInfo.name, - ModelInfoMap{modelInfo.numStateBytes, std::move(states), - modelInfo.initialFnSym}}); + modelMap.insert( + {modelInfo.name, + ModelInfoMap{modelInfo.numStateBytes, std::move(states), + modelInfo.initialFnSym, modelInfo.finalFnSym}}); } patterns.add initialOps; + SmallVector finalOps; SmallVector passthroughOps; SmallVector clocks; modelOp.walk([&](Operation *op) { @@ -79,6 +80,10 @@ LogicalResult LowerClocksToFuncsPass::lowerModel(ModelOp modelOp) { initialOps.push_back(initOp); clocks.push_back(initOp); }) + .Case([&](auto op) { + finalOps.push_back(op); + clocks.push_back(op); + }) .Case([&](auto ptOp) { passthroughOps.push_back(ptOp); clocks.push_back(ptOp); @@ -99,7 +104,13 @@ LogicalResult LowerClocksToFuncsPass::lowerModel(ModelOp modelOp) { for (auto initOp : initialOps) diag.attachNote(initOp.getLoc()) << "Conflicting InitialOp:"; } - if (passthroughOps.size() > 1 || initialOps.size() > 1) + if (finalOps.size() > 1) { + auto diag = modelOp.emitOpError() + << "containing multiple FinalOps is currently unsupported."; + for (auto op : finalOps) + diag.attachNote(op.getLoc()) << "Conflicting FinalOp:"; + } + if (passthroughOps.size() > 1 || initialOps.size() > 1 || finalOps.size() > 1) return failure(); // Perform the actual extraction. @@ -115,7 +126,7 @@ LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp, Value modelStorageArg, OpBuilder &funcBuilder) { LLVM_DEBUG(llvm::dbgs() << "- Lowering clock " << clockOp->getName() << "\n"); - assert((isa(clockOp))); + assert((isa(clockOp))); // Add a `StorageType` block argument to the clock's body block which we are // going to use to pass the storage pointer to the clock once it has been @@ -141,6 +152,8 @@ LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp, funcName.append("_passthrough"); else if (isa(clockOp)) funcName.append("_initial"); + else if (isa(clockOp)) + funcName.append("_final"); else funcName.append("_clock"); @@ -172,6 +185,13 @@ LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp, << "' will be overridden."; modelOp.setInitialFnAttr( FlatSymbolRefAttr::get(funcOp.getSymNameAttr())); + }) + .Case([&](auto) { + if (modelOp.getFinalFn().has_value()) + modelOp.emitWarning() + << "Existing model finalizer '" + << modelOp.getFinalFnAttr().getValue() << "' will be overridden."; + modelOp.setFinalFnAttr(FlatSymbolRefAttr::get(funcOp.getSymNameAttr())); }); // Move the clock's body block to the function and remove the old clock op. diff --git a/lib/Dialect/Arc/Transforms/LowerState.cpp b/lib/Dialect/Arc/Transforms/LowerState.cpp index f98a1807ed97..8784c55ab2c8 100644 --- a/lib/Dialect/Arc/Transforms/LowerState.cpp +++ b/lib/Dialect/Arc/Transforms/LowerState.cpp @@ -943,9 +943,10 @@ LogicalResult LowerStatePass::runOnModule(HWModuleOp moduleOp, moduleOp.getBodyBlock()->eraseArguments( [&](auto arg) { return arg != lowering.storageArg; }); ImplicitLocOpBuilder builder(moduleOp.getLoc(), moduleOp); - auto modelOp = builder.create( - moduleOp.getLoc(), moduleOp.getModuleNameAttr(), - TypeAttr::get(moduleOp.getModuleType()), mlir::FlatSymbolRefAttr()); + auto modelOp = + builder.create(moduleOp.getLoc(), moduleOp.getModuleNameAttr(), + TypeAttr::get(moduleOp.getModuleType()), + FlatSymbolRefAttr{}, FlatSymbolRefAttr{}); modelOp.getBody().takeBody(moduleOp.getBody()); moduleOp->erase(); sortTopologically(&modelOp.getBodyBlock()); diff --git a/test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir b/test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir index a9100f6b9430..94abfa1bb7ff 100644 --- a/test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir +++ b/test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir @@ -15,20 +15,21 @@ arc.model @NonConstExternalValue io !hw.modty<> { // ----- -func.func @VictimInit(%arg0: !arc.storage<42>) { - return -} +func.func private @Victim(!arc.storage<42>) -// expected-warning @below {{Existing model initializer 'VictimInit' will be overridden.}} -arc.model @ExistingInit io !hw.modty<> initializer @VictimInit { +// expected-warning @below {{Existing model initializer 'Victim' will be overridden.}} +// expected-warning @below {{Existing model finalizer 'Victim' will be overridden.}} +arc.model @ExistingInitializerAndFinalizer io !hw.modty<> initializer @Victim finalizer @Victim { ^bb0(%arg0: !arc.storage<42>): arc.initial {} + arc.final {} } // ----- // expected-error @below {{op containing multiple PassThroughOps cannot be lowered.}} // expected-error @below {{op containing multiple InitialOps is currently unsupported.}} +// expected-error @below {{op containing multiple FinalOps is currently unsupported.}} arc.model @MultiInitAndPassThrough io !hw.modty<> { ^bb0(%arg0: !arc.storage<1>): // expected-note @below {{Conflicting PassThroughOp:}} @@ -37,6 +38,10 @@ arc.model @MultiInitAndPassThrough io !hw.modty<> { arc.initial {} // expected-note @below {{Conflicting PassThroughOp:}} arc.passthrough {} + // expected-note @below {{Conflicting FinalOp:}} + arc.final {} // expected-note @below {{Conflicting InitialOp:}} arc.initial {} + // expected-note @below {{Conflicting FinalOp:}} + arc.final {} } diff --git a/test/Dialect/Arc/lower-clocks-to-funcs.mlir b/test/Dialect/Arc/lower-clocks-to-funcs.mlir index 9dd7d8899965..22a954cffb32 100644 --- a/test/Dialect/Arc/lower-clocks-to-funcs.mlir +++ b/test/Dialect/Arc/lower-clocks-to-funcs.mlir @@ -1,31 +1,37 @@ // RUN: circt-opt %s --arc-lower-clocks-to-funcs --verify-diagnostics | FileCheck %s // CHECK-LABEL: func.func @Trivial_clock(%arg0: !arc.storage<42>) { -// CHECK-NEXT: %true = hw.constant true -// CHECK-NEXT: %c0_i9001 = hw.constant 0 : i9001 -// CHECK-NEXT: %0 = comb.mux %true, %c0_i9001, %c0_i9001 : i9001 +// CHECK-NEXT: [[TMP:%.+]] = hw.constant 9001 +// CHECK-NEXT: call @DummyB([[TMP]]) {a} // CHECK-NEXT: return // CHECK-NEXT: } // CHECK-LABEL: func.func @Trivial_passthrough(%arg0: !arc.storage<42>) { -// CHECK-NEXT: %true = hw.constant true -// CHECK-NEXT: %c1_i9001 = hw.constant 1 : i9001 -// CHECK-NEXT: %0 = comb.mux %true, %c1_i9001, %c1_i9001 : i9001 +// CHECK-NEXT: [[TMP:%.+]] = hw.constant 9002 +// CHECK-NEXT: call @DummyB([[TMP]]) {b} // CHECK-NEXT: return // CHECK-NEXT: } // CHECK-LABEL: func.func @Trivial_initial(%arg0: !arc.storage<42>) { -// CHECK-NEXT: %true = hw.constant true -// CHECK-NEXT: %c1_i9002 = hw.constant 1 : i9002 -// CHECK-NEXT: %0 = comb.mux %true, %c1_i9002, %c1_i9002 : i9002 -// CHECK-NEXT: call @Trivial_passthrough(%arg0) : (!arc.storage<42>) -> () +// CHECK-NEXT: [[TMP:%.+]] = hw.constant 9003 +// CHECK-NEXT: call @DummyB([[TMP]]) {c} +// CHECK-NEXT: call @Trivial_passthrough(%arg0) +// CHECK-NEXT: return +// CHECK-NEXT: } + +// CHECK-LABEL: func.func @Trivial_final(%arg0: !arc.storage<42>) { +// CHECK-NEXT: [[TMP:%.+]] = hw.constant 9004 +// CHECK-NEXT: call @DummyB([[TMP]]) {d} // CHECK-NEXT: return // CHECK-NEXT: } -// CHECK-LABEL: arc.model @Trivial io !hw.modty<> initializer @Trivial_initial { +// CHECK-LABEL: arc.model @Trivial +// CHECK-SAME: io !hw.modty<> +// CHECK-SAME: initializer @Trivial_initial +// CHECK-SAME: finalizer @Trivial_final +// CHECK-SAME: { // CHECK-NEXT: ^bb0(%arg0: !arc.storage<42>): // CHECK-NEXT: %true = hw.constant true -// CHECK-NEXT: %false = hw.constant false // CHECK-NEXT: scf.if %true { // CHECK-NEXT: func.call @Trivial_clock(%arg0) : (!arc.storage<42>) -> () // CHECK-NEXT: } @@ -35,21 +41,27 @@ arc.model @Trivial io !hw.modty<> { ^bb0(%arg0: !arc.storage<42>): %true = hw.constant true - %false = hw.constant false + %0 = hw.constant 9001 : i42 + %1 = hw.constant 9002 : i42 + %2 = hw.constant 9003 : i42 + %3 = hw.constant 9004 : i42 arc.clock_tree %true { - %c0_i9001 = hw.constant 0 : i9001 - %0 = comb.mux %true, %c0_i9001, %c0_i9001 : i9001 + func.call @DummyB(%0) {a} : (i42) -> () } arc.passthrough { - %c1_i9001 = hw.constant 1 : i9001 - %0 = comb.mux %true, %c1_i9001, %c1_i9001 : i9001 + func.call @DummyB(%1) {b} : (i42) -> () } arc.initial { - %c1_i9002 = hw.constant 1 : i9002 - %0 = comb.mux %true, %c1_i9002, %c1_i9002 : i9002 + func.call @DummyB(%2) {c} : (i42) -> () + } + arc.final { + func.call @DummyB(%3) {d} : (i42) -> () } } +func.func private @DummyA() -> i42 +func.func private @DummyB(i42) -> () + //===----------------------------------------------------------------------===// // CHECK-LABEL: func.func @NestedRegions_passthrough(%arg0: !arc.storage<42>) {