diff --git a/include/circt/Dialect/Arc/ArcConstants.h b/include/circt/Dialect/Arc/ArcConstants.h new file mode 100644 index 000000000000..ec8e3a0dbe83 --- /dev/null +++ b/include/circt/Dialect/Arc/ArcConstants.h @@ -0,0 +1,27 @@ +//===- ArcConstants.h - Declare Arc dialect constants ------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ARC_ARCCONSTANTS_H +#define CIRCT_DIALECT_ARC_ARCCONSTANTS_H + +namespace circt { +namespace arc { + +// Offset for model state allocations. The first 16 bytes of model storage are +// reserved for the model header. The first 8 bytes contain the current +// simulation time. The next 8 bytes are reserved for the termination flag used +// by `SimTerminateOp`. The actual model state starts at offset 16. +inline constexpr unsigned kTimeOffset = 0; +inline constexpr unsigned kTerminateFlagOffset = 8; +inline constexpr unsigned kStateOffset = 16; + +} // namespace arc + +} // namespace circt + +#endif // CIRCT_DIALECT_ARC_ARCCONSTANTS_H diff --git a/include/circt/Dialect/Arc/ArcOps.td b/include/circt/Dialect/Arc/ArcOps.td index 265777b78edf..075afbdbaaf0 100644 --- a/include/circt/Dialect/Arc/ArcOps.td +++ b/include/circt/Dialect/Arc/ArcOps.td @@ -626,6 +626,27 @@ def CurrentTimeOp : ArcOp<"current_time", [ }]; } +def TerminateOp : ArcOp<"terminate", [ + MemoryEffects<[MemWrite]> +]> { + let summary = "Request simulation termination"; + let description = [{ + Sets a termination flag in the model's storage. + It unconditionally writes 1 for success or 2 for failure. + This allows the simulation to exit gracefully after the current + evaluation cycle. + }]; + + let arguments = (ins + StorageType:$storage, + BoolAttr:$success + ); + + let assemblyFormat = [{ + $storage `,` $success attr-dict `:` qualified(type($storage)) + }]; +} + //===----------------------------------------------------------------------===// // Procedural Ops //===----------------------------------------------------------------------===// diff --git a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp index 039b54da5bcf..0efedc1e8a52 100644 --- a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp +++ b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp @@ -10,6 +10,7 @@ #include "circt/Conversion/CombToArith.h" #include "circt/Conversion/CombToLLVM.h" #include "circt/Conversion/HWToLLVM.h" +#include "circt/Dialect/Arc/ArcConstants.h" #include "circt/Dialect/Arc/ArcOps.h" #include "circt/Dialect/Arc/ModelInfo.h" #include "circt/Dialect/Arc/Runtime/Common.h" @@ -37,6 +38,7 @@ #include "mlir/Dialect/LLVMIR/LLVMAttrs.h" #include "mlir/Dialect/LLVMIR/LLVMDialect.h" #include "mlir/Dialect/SCF/IR/SCF.h" +#include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinDialect.h" #include "mlir/Pass/Pass.h" #include "mlir/Transforms/DialectConversion.h" @@ -987,6 +989,32 @@ struct SimPrintFormattedProcOpLowering StringCache &stringCache; }; +struct TerminateOpLowering : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(arc::TerminateOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto loc = op.getLoc(); + + auto i8Type = rewriter.getI8Type(); + auto ptrType = LLVM::LLVMPointerType::get(rewriter.getContext()); + + Value flagPtr = LLVM::GEPOp::create( + rewriter, loc, ptrType, i8Type, adaptor.getStorage(), + ArrayRef{arc::kTerminateFlagOffset}); + + uint8_t statusCode = op.getSuccess() ? 1 : 2; + Value codeVal = LLVM::ConstantOp::create( + rewriter, loc, i8Type, rewriter.getI8IntegerAttr(statusCode)); + + LLVM::StoreOp::create(rewriter, loc, codeVal, flagPtr); + + rewriter.eraseOp(op); + return success(); + } +}; + } // namespace static LogicalResult convert(arc::ExecuteOp op, arc::ExecuteOp::Adaptor adaptor, @@ -1403,6 +1431,7 @@ void LowerArcToLLVMPass::runOnOperation() { StateReadOpLowering, StateWriteOpLowering, StorageGetOpLowering, + TerminateOpLowering, TimeToIntOpLowering, ZeroCountOpLowering >(converter, &getContext()); diff --git a/lib/Dialect/Arc/Transforms/AllocateState.cpp b/lib/Dialect/Arc/Transforms/AllocateState.cpp index dae96c484a13..72d67682c088 100644 --- a/lib/Dialect/Arc/Transforms/AllocateState.cpp +++ b/lib/Dialect/Arc/Transforms/AllocateState.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +#include "circt/Dialect/Arc/ArcConstants.h" #include "circt/Dialect/Arc/ArcOps.h" #include "circt/Dialect/Arc/ArcPasses.h" #include "mlir/IR/ImplicitLocOpBuilder.h" @@ -14,10 +15,6 @@ #define DEBUG_TYPE "arc-allocate-state" -// Offset for model state allocations. The first bytes of model storage are -// reserved for the model header (currently just the i64 simulation time). -static constexpr unsigned kStateOffset = 8; - namespace circt { namespace arc { #define GEN_PASS_DEF_ALLOCATESTATE diff --git a/lib/Dialect/Arc/Transforms/LowerState.cpp b/lib/Dialect/Arc/Transforms/LowerState.cpp index b6bb7ef31cf9..9b949349dfdb 100644 --- a/lib/Dialect/Arc/Transforms/LowerState.cpp +++ b/lib/Dialect/Arc/Transforms/LowerState.cpp @@ -103,6 +103,7 @@ struct OpLowering { LogicalResult lower(seq::InitialOp op); LogicalResult lower(llhd::FinalOp op); LogicalResult lower(llhd::CurrentTimeOp op); + LogicalResult lower(sim::ClockedTerminateOp op); scf::IfOp createIfClockOp(Value clock); @@ -223,7 +224,8 @@ LogicalResult ModuleLowering::run() { // Lower the ops. for (auto &op : moduleOp.getOps()) { - if (mlir::isMemoryEffectFree(&op) && !isa(op)) + if (mlir::isMemoryEffectFree(&op) && + !isa(op)) continue; if (isa(op)) continue; // handled as part of `MemoryOp` @@ -417,8 +419,8 @@ LogicalResult OpLowering::lower() { return TypeSwitch(op) // Operations with special lowering. .Case( - [&](auto op) { return lower(op); }) + seq::InitialOp, llhd::FinalOp, llhd::CurrentTimeOp, + sim::ClockedTerminateOp>([&](auto op) { return lower(op); }) // Operations that should be skipped entirely and never land on the // worklist to be lowered. @@ -1044,6 +1046,37 @@ LogicalResult OpLowering::lower(llhd::CurrentTimeOp op) { return success(); } +LogicalResult OpLowering::lower(sim::ClockedTerminateOp op) { + if (phase != Phase::New) + return success(); + + if (initial) + return success(); + + auto ifClockOp = createIfClockOp(op.getClock()); + if (!ifClockOp) + return failure(); + + OpBuilder::InsertionGuard guard(module.builder); + module.builder.setInsertionPoint(ifClockOp.thenYield()); + + auto loc = op.getLoc(); + Value cond = lowerValue(op.getCondition(), phase); + if (!cond) + return op.emitOpError("Failed to lower condition"); + + auto ifOp = createOrReuseIf(module.builder, cond, false); + if (!ifOp) + return op.emitOpError("Failed to create condition block"); + + module.builder.setInsertionPoint(ifOp.thenYield()); + + arc::TerminateOp::create(module.builder, loc, module.storageArg, + op.getSuccessAttr()); + + return success(); +} + /// Create the operations necessary to detect a posedge on the given clock, /// potentially reusing a previous posedge detection, and create an `scf.if` /// operation for that posedge. This also tries to reuse an `scf.if` operation diff --git a/test/Conversion/ArcToLLVM/lower-arc-to-llvm.mlir b/test/Conversion/ArcToLLVM/lower-arc-to-llvm.mlir index e6f2e5be2c8b..cb0571924de7 100644 --- a/test/Conversion/ArcToLLVM/lower-arc-to-llvm.mlir +++ b/test/Conversion/ArcToLLVM/lower-arc-to-llvm.mlir @@ -349,3 +349,26 @@ func.func @Time(%arg0: !arc.storage<42>) -> (i64, !llhd.time, i64) { // CHECK-NEXT: llvm.return [[TMP4]] return %0, %1, %2 : i64, !llhd.time, i64 } + + +// CHECK-LABEL: llvm.func @test_success_eval +// CHECK-SAME: (%[[STATE:.*]]: !llvm.ptr, %[[COND:.*]]: i1) +func.func @test_success_eval(%state: !arc.storage, %cond: i1) { + // CHECK-NEXT: %[[GEP:.*]] = llvm.getelementptr %[[STATE]][8] : (!llvm.ptr) -> !llvm.ptr, i8 + // CHECK-NEXT: %[[VAL:.*]] = llvm.mlir.constant(1 : i8) : i8 + // CHECK-NEXT: llvm.store %[[VAL]], %[[GEP]] : i8, !llvm.ptr + // CHECK-NEXT: llvm.return + arc.terminate %state, true : !arc.storage + return +} + +// CHECK-LABEL: llvm.func @test_failure_eval +// CHECK-SAME: (%[[STATE:.*]]: !llvm.ptr, %[[COND:.*]]: i1) +func.func @test_failure_eval(%state: !arc.storage, %cond: i1) { + // CHECK-NEXT: %[[GEP_FAIL:.*]] = llvm.getelementptr %[[STATE]][8] : (!llvm.ptr) -> !llvm.ptr, i8 + // CHECK-NEXT: %[[VAL_FAIL:.*]] = llvm.mlir.constant(2 : i8) : i8 + // CHECK-NEXT: llvm.store %[[VAL_FAIL]], %[[GEP_FAIL]] : i8, !llvm.ptr + // CHECK-NEXT: llvm.return + arc.terminate %state, false : !arc.storage + return +} diff --git a/test/Dialect/Arc/allocate-state.mlir b/test/Dialect/Arc/allocate-state.mlir index 8fc7de93cf13..8b50561e4608 100644 --- a/test/Dialect/Arc/allocate-state.mlir +++ b/test/Dialect/Arc/allocate-state.mlir @@ -102,16 +102,16 @@ arc.model @test io !hw.modty { } // CHECK-LABEL: arc.model @StructPadding -// CHECK-NEXT: !arc.storage<12> +// CHECK-NEXT: !arc.storage<20> arc.model @StructPadding io !hw.modty<> { ^bb0(%arg0: !arc.storage): - // This !hw.struct is only 11 bits wide, but mapped to an !llvm.struct, each + // This !hw.struct is only 19 bits wide, but mapped to an !llvm.struct, each // field gets byte-aligned. arc.alloc_state %arg0 : (!arc.storage) -> !arc.state> } // CHECK-LABEL: arc.model @ArrayPadding -// CHECK-NEXT: !arc.storage<12> +// CHECK-NEXT: !arc.storage<20> arc.model @ArrayPadding io !hw.modty<> { ^bb0(%arg0: !arc.storage): // This !hw.array is only 18 bits wide, but mapped to an !llvm.array, each diff --git a/test/Dialect/Arc/lower-sim.mlir b/test/Dialect/Arc/lower-sim.mlir index 04e1b547d546..2b2713dde7e6 100644 --- a/test/Dialect/Arc/lower-sim.mlir +++ b/test/Dialect/Arc/lower-sim.mlir @@ -18,16 +18,16 @@ module { // CHECK: %[[format_str_ptr:.*]] = llvm.mlir.addressof @[[format_str]] : !llvm.ptr // CHECK-DAG: %[[c:.*]] = llvm.mlir.constant(24 : i8) // CHECK-DAG: %[[zero:.*]] = llvm.mlir.constant(0 : i8) - // CHECK-DAG: %[[size:.*]] = llvm.mlir.constant(11 : i64) + // CHECK-DAG: %[[size:.*]] = llvm.mlir.constant(19 : i64) // CHECK-DAG: %[[tstep:.*]] = llvm.mlir.constant(321 : i64) // CHECK-DAG: %[[state:.*]] = llvm.call @malloc(%[[size:.*]]) : // CHECK: "llvm.intr.memset"(%[[state]], %[[zero]], %[[size]]) <{isVolatile = false}> arc.sim.instantiate @id as %model { - // CHECK-NEXT: %[[i_ptr:.*]] = llvm.getelementptr %[[state]][8] : (!llvm.ptr) -> !llvm.ptr, i8 + // CHECK-NEXT: %[[i_ptr:.*]] = llvm.getelementptr %[[state]][16] : (!llvm.ptr) -> !llvm.ptr, i8 // CHECK-NEXT: llvm.store %[[c]], %[[i_ptr]] : i8 arc.sim.set_input %model, "i" = %c : i8, !arc.sim.instance<@id> - // CHECK-NEXT: %[[j_ptr:.*]] = llvm.getelementptr %[[state]][9] : (!llvm.ptr) -> !llvm.ptr, i8 + // CHECK-NEXT: %[[j_ptr:.*]] = llvm.getelementptr %[[state]][17] : (!llvm.ptr) -> !llvm.ptr, i8 // CHECK-NEXT: llvm.store %[[c]], %[[j_ptr]] : i8 arc.sim.set_input %model, "j" = %c : i8, !arc.sim.instance<@id> @@ -40,7 +40,7 @@ module { // CHECK-NEXT: llvm.store %[[tnew]], %[[state]] : i64, !llvm.ptr arc.sim.step %model by %tstep : !arc.sim.instance<@id> - // CHECK-NEXT: %[[o_ptr:.*]] = llvm.getelementptr %[[state]][10] : (!llvm.ptr) -> !llvm.ptr, i8 + // CHECK-NEXT: %[[o_ptr:.*]] = llvm.getelementptr %[[state]][18] : (!llvm.ptr) -> !llvm.ptr, i8 // CHECK-NEXT: %[[result:.*]] = llvm.load %[[o_ptr]] : !llvm.ptr -> i8 %result = arc.sim.get_port %model, "o" : i8, !arc.sim.instance<@id> diff --git a/test/Dialect/Arc/lower-state.mlir b/test/Dialect/Arc/lower-state.mlir index f1b342ed02ca..a3dfb10895c0 100644 --- a/test/Dialect/Arc/lower-state.mlir +++ b/test/Dialect/Arc/lower-state.mlir @@ -717,3 +717,55 @@ hw.module @LLHDTimeOps(in %clock: !seq.clock, out t: i64) { hw.output %1 : i64 } + +// CHECK-LABEL: arc.model @TestSimToArcTerminateSuccess +// CHECK-SAME: io !hw.modty +hw.module @TestSimToArcTerminateSuccess(in %clock: !seq.clock, in %cond: i1) { + // CHECK: %[[IN_CLK:.*]] = arc.root_input "clock" + // CHECK: %[[IN_COND:.*]] = arc.root_input "cond" + // CHECK: %[[LAST_CLK_PTR:.*]] = arc.alloc_state %arg0 + + // CHECK: %[[CLK_VAL:.*]] = arc.state_read %[[IN_CLK]] : + // CHECK: %[[CURR_CLK:.*]] = seq.from_clock %[[CLK_VAL]] + + // CHECK: %[[LAST_CLK_VAL:.*]] = arc.state_read %[[LAST_CLK_PTR]] : + // CHECK: arc.state_write %[[LAST_CLK_PTR]] = %[[CURR_CLK]] : + + // CHECK: %[[EDGE_XOR:.*]] = comb.xor %[[LAST_CLK_VAL]], %[[CURR_CLK]] : i1 + // CHECK: %[[POSEDGE:.*]] = comb.and %[[EDGE_XOR]], %[[CURR_CLK]] : i1 + + // CHECK: scf.if %[[POSEDGE]] { + // CHECK: %[[COND_VAL:.*]] = arc.state_read %[[IN_COND]] : + // CHECK: scf.if %[[COND_VAL]] { + // CHECK: arc.terminate %arg0, true : !arc.storage + // CHECK: } + // CHECK: } + + sim.clocked_terminate %clock, %cond, success, verbose +} + +// CHECK-LABEL: arc.model @TestSimToArcTerminateFailure +// CHECK-SAME: io !hw.modty +hw.module @TestSimToArcTerminateFailure(in %clock: !seq.clock, in %cond: i1) { + // CHECK: %[[IN_CLK:.*]] = arc.root_input "clock" + // CHECK: %[[IN_COND:.*]] = arc.root_input "cond" + // CHECK: %[[LAST_CLK_PTR:.*]] = arc.alloc_state %arg0 + + // CHECK: %[[CLK_VAL:.*]] = arc.state_read %[[IN_CLK]] : + // CHECK: %[[CURR_CLK:.*]] = seq.from_clock %[[CLK_VAL]] + + // CHECK: %[[LAST_CLK_VAL:.*]] = arc.state_read %[[LAST_CLK_PTR]] : + // CHECK: arc.state_write %[[LAST_CLK_PTR]] = %[[CURR_CLK]] : + + // CHECK: %[[EDGE_XOR:.*]] = comb.xor %[[LAST_CLK_VAL]], %[[CURR_CLK]] : i1 + // CHECK: %[[POSEDGE:.*]] = comb.and %[[EDGE_XOR]], %[[CURR_CLK]] : i1 + + // CHECK: scf.if %[[POSEDGE]] { + // CHECK: %[[COND_VAL:.*]] = arc.state_read %[[IN_COND]] : + // CHECK: scf.if %[[COND_VAL]] { + // CHECK: arc.terminate %arg0, false : !arc.storage + // CHECK: } + // CHECK: } + + sim.clocked_terminate %clock, %cond, failure, verbose +}