Skip to content
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
31 changes: 30 additions & 1 deletion include/circt/Dialect/Arc/ArcOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def DefineOp : ArcOp<"define", [

def OutputOp : ArcOp<"output", [
Terminator,
ParentOneOf<["DefineOp", "LutOp", "ClockDomainOp"]>,
ParentOneOf<["DefineOp", "LutOp", "ClockDomainOp", "ExecuteOp"]>,
Pure,
ReturnLike
]> {
Expand Down Expand Up @@ -599,6 +599,35 @@ def StateWriteOp : ArcOp<"state_write", [
}];
}

//===----------------------------------------------------------------------===//
// Procedural Ops
//===----------------------------------------------------------------------===//

def ExecuteOp : ArcOp<"execute", [
IsolatedFromAbove,
RecursiveMemoryEffects,
]> {
let summary = "Execute an SSACFG region";
let description = [{
The `arc.execute` op allows an SSACFG region to be embedded in a parent
graph region, or another SSACFG region. Whenever execution reaches this op,
Copy link
Member

Choose a reason for hiding this comment

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

Could you explain what execution reaching the op means in this context? Am I correct in understanding that it means "when the value of the result is needed" in an intentionally non-specific way?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's exactly it 😁. I think the deal with hw.module and graph regions in Arcilator is that all side-effecting operations are always executed (unless the compiler can prove that not executing them has no observable effect), and pure operations are executed opportunistically when the results are needed. Although that last part is kind of redundant; maybe we could just say that all ops are always executed, unless the compiler can prove that skipping them has no observable effect. Or something about the side effects of operations in the module take effect in the order they appear in the module.

its body region is executed and the results yielded from the body are
produced as the `arc.execute` op's results. The op is isolated from above.
Any SSA values defined outside the op that are used inside the body have to
be captured as operands and then referred to as entry block arguments in the
body.
}];
let arguments = (ins Variadic<AnyType>:$inputs);
let results = (outs Variadic<AnyType>:$results);
let regions = (region MinSizedRegion<1>:$body);
let assemblyFormat = [{
(` ` `(` $inputs^ `:` type($inputs) `)`)?
(`->` `(` type($results)^ `)`)?
attr-dict-with-keyword $body
}];
let hasRegionVerifier = 1;
}

//===----------------------------------------------------------------------===//
// Simulation Orchestration
//===----------------------------------------------------------------------===//
Expand Down
9 changes: 9 additions & 0 deletions lib/Dialect/Arc/ArcOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,15 @@ LogicalResult SimStepOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
return success();
}

//===----------------------------------------------------------------------===//
// ExecuteOp
//===----------------------------------------------------------------------===//

LogicalResult ExecuteOp::verifyRegions() {
return verifyTypeListEquivalence(*this, getInputs().getTypes(),
getBody().getArgumentTypes(), "input");
}

#include "circt/Dialect/Arc/ArcInterfaces.cpp.inc"

#define GET_OP_CLASSES
Expand Down
6 changes: 5 additions & 1 deletion lib/Dialect/Arc/Transforms/LowerArcsToFuncs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ struct OutputOpLowering : public OpConversionPattern<arc::OutputOp> {
LogicalResult
matchAndRewrite(arc::OutputOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const final {
if (!isa<arc::DefineOp, func::FuncOp>(op->getParentOp()))
return failure();
rewriter.replaceOpWithNewOp<func::ReturnOp>(op, adaptor.getOutputs());
return success();
}
Expand Down Expand Up @@ -101,7 +103,9 @@ static void populateLegality(ConversionTarget &target) {

target.addIllegalOp<arc::CallOp>();
target.addIllegalOp<arc::DefineOp>();
target.addIllegalOp<arc::OutputOp>();
target.addDynamicallyLegalOp<arc::OutputOp>([](auto op) {
return !isa<arc::DefineOp, func::FuncOp>(op->getParentOp());
});
target.addIllegalOp<arc::StateOp>();
}

Expand Down
34 changes: 34 additions & 0 deletions test/Dialect/Arc/basic-errors.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -590,3 +590,37 @@ hw.module @InvalidInitType(in %clock: !seq.clock, in %input: i7) {
// expected-error @below {{failed to verify that types of initial arguments match result types}}
%res = arc.state @Bar(%input) clock %clock initial (%cst: i8) latency 1 : (i7) -> i7
}

// -----

// expected-error @below {{region with at least 1 blocks}}
arc.execute {
}

// -----

%0 = hw.constant 0 : i42
// expected-error @below {{input type mismatch: input #0}}
// expected-note @below {{expected type: 'i42'}}
// expected-note @below {{actual type: 'i19'}}
arc.execute (%0 : i42) {
^bb0(%arg0: i19):
arc.output
}

// -----

arc.execute -> (i42) {
// expected-error @below {{incorrect number of outputs: expected 1, but got 0}}
arc.output
}

// -----

arc.execute -> (i42) {
%0 = hw.constant 0 : i19
// expected-error @below {{output type mismatch: output #0}}
// expected-note @below {{expected type: 'i42'}}
// expected-note @below {{actual type: 'i19'}}
arc.output %0 : i19
}
25 changes: 24 additions & 1 deletion test/Dialect/Arc/basic.mlir
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: circt-opt %s --verify-diagnostics | circt-opt | FileCheck %s
// RUN: circt-opt --verify-diagnostics --verify-roundtrip %s | circt-opt | FileCheck %s

// CHECK-LABEL: arc.define @Foo
arc.define @Foo(%arg0: i42, %arg1: i9) -> (i42, i9) {
Expand Down Expand Up @@ -370,3 +370,26 @@ func.func @ReadsWrites(%arg0: !arc.state<i42>, %arg1: i42, %arg2: i1) {
arc.state_write %arg0 = %arg1 if %arg2 : <i42>
return
}

func.func @Execute(%arg0: i42) {
// CHECK: arc.execute {
arc.execute {
arc.output
}
// CHECK: arc.execute (%arg0 : i42) {
arc.execute (%arg0 : i42) {
^bb0(%0: i42):
arc.output
}
// CHECK: arc.execute -> (i42) {
arc.execute -> (i42) {
%0 = hw.constant 1337 : i42
arc.output %0 : i42
}
// CHECK: arc.execute (%arg0 : i42) -> (i42) {
arc.execute (%arg0 : i42) -> (i42) {
^bb0(%0: i42):
arc.output %0 : i42
}
return
}
11 changes: 11 additions & 0 deletions test/Dialect/Arc/lower-arcs-to-funcs.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,14 @@ arc.define @sub1(%arg0: i32) -> i32 {
arc.output %arg0 : i32
// CHECK-NEXT: return %arg0 : i32
}

// CHECK-LABEL: hw.module @DontConvertExecuteOps
hw.module @DontConvertExecuteOps(in %arg0: i32, out out0: i32) {
// CHECK: arc.execute
// CHECK: arc.output
%0 = arc.execute (%arg0 : i32) -> (i32) {
^bb0(%1: i32):
arc.output %1 : i32
}
hw.output %0 : i32
}