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
27 changes: 27 additions & 0 deletions include/circt/Dialect/Arc/ArcConstants.h
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions include/circt/Dialect/Arc/ArcOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -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
//===----------------------------------------------------------------------===//
Expand Down
29 changes: 29 additions & 0 deletions lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -987,6 +989,32 @@ struct SimPrintFormattedProcOpLowering
StringCache &stringCache;
};

struct TerminateOpLowering : public OpConversionPattern<arc::TerminateOp> {
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<LLVM::GEPArg>{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,
Expand Down Expand Up @@ -1403,6 +1431,7 @@ void LowerArcToLLVMPass::runOnOperation() {
StateReadOpLowering,
StateWriteOpLowering,
StorageGetOpLowering,
TerminateOpLowering,
TimeToIntOpLowering,
ZeroCountOpLowering
>(converter, &getContext());
Expand Down
5 changes: 1 addition & 4 deletions lib/Dialect/Arc/Transforms/AllocateState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down
39 changes: 36 additions & 3 deletions lib/Dialect/Arc/Transforms/LowerState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -223,7 +224,8 @@ LogicalResult ModuleLowering::run() {

// Lower the ops.
for (auto &op : moduleOp.getOps()) {
if (mlir::isMemoryEffectFree(&op) && !isa<hw::OutputOp>(op))
if (mlir::isMemoryEffectFree(&op) &&
!isa<hw::OutputOp, sim::ClockedTerminateOp>(op))
continue;
if (isa<MemoryReadPortOp, MemoryWritePortOp>(op))
continue; // handled as part of `MemoryOp`
Expand Down Expand Up @@ -417,8 +419,8 @@ LogicalResult OpLowering::lower() {
return TypeSwitch<Operation *, LogicalResult>(op)
// Operations with special lowering.
.Case<StateOp, sim::DPICallOp, MemoryOp, TapOp, InstanceOp, hw::OutputOp,
seq::InitialOp, llhd::FinalOp, llhd::CurrentTimeOp>(
[&](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.
Expand Down Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions test/Conversion/ArcToLLVM/lower-arc-to-llvm.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
6 changes: 3 additions & 3 deletions test/Dialect/Arc/allocate-state.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,16 @@ arc.model @test io !hw.modty<input x : i1, output y : i1> {
}

// 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<!hw.struct<tag: i5, sign_ext: i1, offset: i3, size: i2>>
}

// 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
Expand Down
8 changes: 4 additions & 4 deletions test/Dialect/Arc/lower-sim.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand All @@ -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>

Expand Down
52 changes: 52 additions & 0 deletions test/Dialect/Arc/lower-state.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -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<input clock : !seq.clock, input cond : i1>
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]] : <!seq.clock>
// CHECK: %[[CURR_CLK:.*]] = seq.from_clock %[[CLK_VAL]]

// CHECK: %[[LAST_CLK_VAL:.*]] = arc.state_read %[[LAST_CLK_PTR]] : <i1>
// CHECK: arc.state_write %[[LAST_CLK_PTR]] = %[[CURR_CLK]] : <i1>

// 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]] : <i1>
// 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<input clock : !seq.clock, input cond : i1>
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]] : <!seq.clock>
// CHECK: %[[CURR_CLK:.*]] = seq.from_clock %[[CLK_VAL]]

// CHECK: %[[LAST_CLK_VAL:.*]] = arc.state_read %[[LAST_CLK_PTR]] : <i1>
// CHECK: arc.state_write %[[LAST_CLK_PTR]] = %[[CURR_CLK]] : <i1>

// 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]] : <i1>
// CHECK: scf.if %[[COND_VAL]] {
// CHECK: arc.terminate %arg0, false : !arc.storage
// CHECK: }
// CHECK: }

sim.clocked_terminate %clock, %cond, failure, verbose
}
Loading