Skip to content

Commit

Permalink
[Arc] Add arc.final op
Browse files Browse the repository at this point in the history
Add the `arc.final` op to complement the existing `arc.initial`. The
body of `arc.final` is executed after the last time step, when the
simulation is shut down. This corresponds to SystemVerilog's `final`
procedure, and can also be used to report statistics about which `cover`
statements were hit, or to check if an `eventually` property has been
satisfied.

The `arc.final` op is extracted into a `*_final` function. The contract
with the user is that the `*_final` function must be called exactly once
after the last call to `*_eval`. The simulation lifetime now looks like
this:

```
design_initial()
design_eval()
design_eval()
design_eval()
...
design_eval()
design_final()
```

The `arc.sim.instantiate` op inserts a corresponding call to `*_final`
when lowering to LLVM.
  • Loading branch information
fabianschuiki committed Oct 14, 2024
1 parent 2a8231b commit 827c4d8
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 38 deletions.
17 changes: 14 additions & 3 deletions include/circt/Dialect/Arc/ArcOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -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
}];
Expand Down Expand Up @@ -672,11 +679,15 @@ def ModelOp : ArcOp<"model", [
}];
let arguments = (ins SymbolNameAttr:$sym_name,
TypeAttrOf<ModuleType>:$io,
OptionalAttr<FlatSymbolRefAttr>:$initialFn);
OptionalAttr<FlatSymbolRefAttr>:$initialFn,
OptionalAttr<FlatSymbolRefAttr>:$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 = [{
Expand Down
7 changes: 5 additions & 2 deletions include/circt/Dialect/Arc/ModelInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,15 @@ struct ModelInfo {
size_t numStateBytes;
llvm::SmallVector<StateInfo> states;
mlir::FlatSymbolRefAttr initialFnSym;
mlir::FlatSymbolRefAttr finalFnSym;

ModelInfo(std::string name, size_t numStateBytes,
llvm::SmallVector<StateInfo> 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
Expand Down
19 changes: 16 additions & 3 deletions lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ struct ModelInfoMap {
size_t numStateBytes;
llvm::DenseMap<StringRef, StateInfo> states;
mlir::FlatSymbolRefAttr initialFnSymbol;
mlir::FlatSymbolRefAttr finalFnSymbol;
};

template <typename OpTy>
Expand Down Expand Up @@ -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<LLVM::CallOp>(loc, finalFnType, model.finalFnSymbol,
ValueRange{allocated});
}

rewriter.create<LLVM::CallOp>(loc, freeFunc, ValueRange{allocated});
rewriter.eraseOp(op);

Expand Down Expand Up @@ -657,9 +669,10 @@ void LowerArcToLLVMPass::runOnOperation() {
llvm::DenseMap<StringRef, StateInfo> 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<SimInstantiateOpLowering, SimSetInputOpLowering,
Expand Down
3 changes: 2 additions & 1 deletion lib/Dialect/Arc/ModelInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ LogicalResult circt::arc::collectModels(mlir::ModuleOp module,
llvm::sort(states, [](auto &a, auto &b) { return a.offset < b.offset; });

models.emplace_back(std::string(modelOp.getName()), storageType.getSize(),
std::move(states), modelOp.getInitialFnAttr());
std::move(states), modelOp.getInitialFnAttr(),
modelOp.getFinalFnAttr());
}

return success();
Expand Down
24 changes: 22 additions & 2 deletions lib/Dialect/Arc/Transforms/LowerClocksToFuncs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ LogicalResult LowerClocksToFuncsPass::lowerModel(ModelOp modelOp) {

// Find the clocks to extract.
SmallVector<InitialOp, 1> initialOps;
SmallVector<FinalOp, 1> finalOps;
SmallVector<PassThroughOp, 1> passthroughOps;
SmallVector<Operation *> clocks;
modelOp.walk([&](Operation *op) {
Expand All @@ -79,6 +80,10 @@ LogicalResult LowerClocksToFuncsPass::lowerModel(ModelOp modelOp) {
initialOps.push_back(initOp);
clocks.push_back(initOp);
})
.Case<FinalOp>([&](auto op) {
finalOps.push_back(op);
clocks.push_back(op);
})
.Case<PassThroughOp>([&](auto ptOp) {
passthroughOps.push_back(ptOp);
clocks.push_back(ptOp);
Expand All @@ -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.
Expand All @@ -115,7 +126,7 @@ LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp,
Value modelStorageArg,
OpBuilder &funcBuilder) {
LLVM_DEBUG(llvm::dbgs() << "- Lowering clock " << clockOp->getName() << "\n");
assert((isa<ClockTreeOp, PassThroughOp, InitialOp>(clockOp)));
assert((isa<ClockTreeOp, PassThroughOp, InitialOp, FinalOp>(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
Expand All @@ -141,6 +152,8 @@ LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp,
funcName.append("_passthrough");
else if (isa<InitialOp>(clockOp))
funcName.append("_initial");
else if (isa<FinalOp>(clockOp))
funcName.append("_final");
else
funcName.append("_clock");

Expand Down Expand Up @@ -172,6 +185,13 @@ LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp,
<< "' will be overridden.";
modelOp.setInitialFnAttr(
FlatSymbolRefAttr::get(funcOp.getSymNameAttr()));
})
.Case<FinalOp>([&](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.
Expand Down
7 changes: 4 additions & 3 deletions lib/Dialect/Arc/Transforms/LowerState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ModelOp>(
moduleOp.getLoc(), moduleOp.getModuleNameAttr(),
TypeAttr::get(moduleOp.getModuleType()), mlir::FlatSymbolRefAttr());
auto modelOp =
builder.create<ModelOp>(moduleOp.getLoc(), moduleOp.getModuleNameAttr(),
TypeAttr::get(moduleOp.getModuleType()),
FlatSymbolRefAttr{}, FlatSymbolRefAttr{});
modelOp.getBody().takeBody(moduleOp.getBody());
moduleOp->erase();
sortTopologically(&modelOp.getBodyBlock());
Expand Down
15 changes: 10 additions & 5 deletions test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -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:}}
Expand All @@ -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 {}
}
50 changes: 31 additions & 19 deletions test/Dialect/Arc/lower-clocks-to-funcs.mlir
Original file line number Diff line number Diff line change
@@ -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: }
Expand All @@ -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>) {
Expand Down

0 comments on commit 827c4d8

Please sign in to comment.