From 98287078174c74541cc88f7badb1b37c8b98e27f Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Wed, 7 Aug 2024 13:51:14 +0900 Subject: [PATCH] [Arc][Sim] Lower Sim DPI func to func.func and support dpi call in Arc (#7386) This PR implements initial support for lowering Sim DPI operations to Arc. * sim::LowerDPIFuncPass implements lowering from `sim.dpi.func` to `func.func` that respects C-level ABI. * arc::LowerStatePass is modified to allocate states and call functions for `sim.dpi.call` op. Currently unclocked call is not supported yet. --- include/circt/Dialect/Sim/SimPasses.td | 5 + integration_test/arcilator/JIT/dpi.mlir | 39 ++++ lib/Conversion/ConvertToArcs/CMakeLists.txt | 1 + .../ConvertToArcs/ConvertToArcs.cpp | 3 +- lib/Dialect/Arc/Transforms/CMakeLists.txt | 1 + lib/Dialect/Arc/Transforms/LowerState.cpp | 93 +++++---- lib/Dialect/Sim/CMakeLists.txt | 1 + lib/Dialect/Sim/SimOps.cpp | 10 +- lib/Dialect/Sim/Transforms/CMakeLists.txt | 3 + lib/Dialect/Sim/Transforms/LowerDPIFunc.cpp | 177 ++++++++++++++++++ test/Dialect/Arc/lower-state.mlir | 13 ++ test/Dialect/Sim/lower-dpi.mlir | 50 +++++ test/Dialect/Sim/round-trip.mlir | 9 +- test/Dialect/Sim/sim-errors.mlir | 9 + tools/arcilator/CMakeLists.txt | 1 + tools/arcilator/arcilator.cpp | 4 + 16 files changed, 378 insertions(+), 41 deletions(-) create mode 100644 integration_test/arcilator/JIT/dpi.mlir create mode 100644 lib/Dialect/Sim/Transforms/LowerDPIFunc.cpp create mode 100644 test/Dialect/Sim/lower-dpi.mlir diff --git a/include/circt/Dialect/Sim/SimPasses.td b/include/circt/Dialect/Sim/SimPasses.td index ae178e8fd42b..b2e6d8e98e23 100644 --- a/include/circt/Dialect/Sim/SimPasses.td +++ b/include/circt/Dialect/Sim/SimPasses.td @@ -23,4 +23,9 @@ def ProceduralizeSim : Pass<"sim-proceduralize", "hw::HWModuleOp"> { let dependentDialects = ["circt::hw::HWDialect, circt::seq::SeqDialect, mlir::scf::SCFDialect"]; } +def LowerDPIFunc : Pass<"sim-lower-dpi-func", "mlir::ModuleOp"> { + let summary = "Lower sim.dpi.func into func.func for the simulation flow"; + let dependentDialects = ["mlir::func::FuncDialect", "mlir::LLVM::LLVMDialect"]; +} + #endif // CIRCT_DIALECT_SIM_SEQPASSES diff --git a/integration_test/arcilator/JIT/dpi.mlir b/integration_test/arcilator/JIT/dpi.mlir new file mode 100644 index 000000000000..8d3d5406ff1b --- /dev/null +++ b/integration_test/arcilator/JIT/dpi.mlir @@ -0,0 +1,39 @@ +// RUN: arcilator %s --run --jit-entry=main | FileCheck %s +// REQUIRES: arcilator-jit + +// CHECK: c = 0 +// CHECK-NEXT: c = 5 +sim.func.dpi @dpi(in %a : i32, in %b : i32, out c : i32) attributes {verilogName = "adder_func"} +func.func @adder_func(%arg0: i32, %arg1: i32, %arg2: !llvm.ptr) { + %0 = arith.addi %arg0, %arg1 : i32 + llvm.store %0, %arg2 : i32, !llvm.ptr + return +} +hw.module @adder(in %clock : i1, in %a : i32, in %b : i32, out c : i32) { + %seq_clk = seq.to_clock %clock + + %0 = sim.func.dpi.call @dpi(%a, %b) clock %seq_clk : (i32, i32) -> i32 + hw.output %0 : i32 +} +func.func @main() { + %c2_i32 = arith.constant 2 : i32 + %c3_i32 = arith.constant 3 : i32 + %one = arith.constant 1 : i1 + %zero = arith.constant 0 : i1 + arc.sim.instantiate @adder as %arg0 { + arc.sim.set_input %arg0, "a" = %c2_i32 : i32, !arc.sim.instance<@adder> + arc.sim.set_input %arg0, "b" = %c3_i32 : i32, !arc.sim.instance<@adder> + arc.sim.set_input %arg0, "clock" = %one : i1, !arc.sim.instance<@adder> + + arc.sim.step %arg0 : !arc.sim.instance<@adder> + arc.sim.set_input %arg0, "clock" = %zero : i1, !arc.sim.instance<@adder> + %0 = arc.sim.get_port %arg0, "c" : i32, !arc.sim.instance<@adder> + arc.sim.emit "c", %0 : i32 + + arc.sim.step %arg0 : !arc.sim.instance<@adder> + arc.sim.set_input %arg0, "clock" = %one : i1, !arc.sim.instance<@adder> + %2 = arc.sim.get_port %arg0, "c" : i32, !arc.sim.instance<@adder> + arc.sim.emit "c", %2 : i32 + } + return +} diff --git a/lib/Conversion/ConvertToArcs/CMakeLists.txt b/lib/Conversion/ConvertToArcs/CMakeLists.txt index 23e318eb6bf2..611b7bfb22ec 100644 --- a/lib/Conversion/ConvertToArcs/CMakeLists.txt +++ b/lib/Conversion/ConvertToArcs/CMakeLists.txt @@ -11,5 +11,6 @@ add_circt_conversion_library(CIRCTConvertToArcs CIRCTArc CIRCTHW CIRCTSeq + CIRCTSim MLIRTransforms ) diff --git a/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp b/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp index b07ebe3285fd..14d01aa843a6 100644 --- a/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp +++ b/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp @@ -10,6 +10,7 @@ #include "circt/Dialect/Arc/ArcOps.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/Seq/SeqOps.h" +#include "circt/Dialect/Sim/SimOps.h" #include "circt/Support/Namespace.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Pass/Pass.h" @@ -25,7 +26,7 @@ using llvm::MapVector; static bool isArcBreakingOp(Operation *op) { return op->hasTrait() || isa(op) || + seq::ClockGateOp, sim::DPICallOp>(op) || op->getNumResults() > 1; } diff --git a/lib/Dialect/Arc/Transforms/CMakeLists.txt b/lib/Dialect/Arc/Transforms/CMakeLists.txt index fc8296c9dc64..ea3892cefb7e 100644 --- a/lib/Dialect/Arc/Transforms/CMakeLists.txt +++ b/lib/Dialect/Arc/Transforms/CMakeLists.txt @@ -35,6 +35,7 @@ add_circt_dialect_library(CIRCTArcTransforms CIRCTOM CIRCTSV CIRCTSeq + CIRCTSim CIRCTSupport MLIRFuncDialect MLIRLLVMDialect diff --git a/lib/Dialect/Arc/Transforms/LowerState.cpp b/lib/Dialect/Arc/Transforms/LowerState.cpp index 0e04ab078419..7f5025b8e457 100644 --- a/lib/Dialect/Arc/Transforms/LowerState.cpp +++ b/lib/Dialect/Arc/Transforms/LowerState.cpp @@ -11,6 +11,7 @@ #include "circt/Dialect/Comb/CombOps.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/Seq/SeqOps.h" +#include "circt/Dialect/Sim/SimOps.h" #include "circt/Support/BackedgeBuilder.h" #include "mlir/Analysis/TopologicalSortUtils.h" #include "mlir/Dialect/Func/IR/FuncOps.h" @@ -117,7 +118,12 @@ struct ModuleLowering { LogicalResult lowerPrimaryInputs(); LogicalResult lowerPrimaryOutputs(); LogicalResult lowerStates(); + template + LogicalResult lowerStateLike(Operation *op, Value clock, Value enable, + Value reset, ArrayRef inputs, + FlatSymbolRefAttr callee); LogicalResult lowerState(StateOp stateOp); + LogicalResult lowerState(sim::DPICallOp dpiCallOp); LogicalResult lowerState(MemoryOp memOp); LogicalResult lowerState(MemoryWritePortOp memWriteOp); LogicalResult lowerState(TapOp tapOp); @@ -139,7 +145,7 @@ static bool shouldMaterialize(Operation *op) { return !isa(op); + StateOp, sim::DPICallOp>(op); } static bool shouldMaterialize(Value value) { @@ -390,53 +396,48 @@ LogicalResult ModuleLowering::lowerPrimaryOutputs() { LogicalResult ModuleLowering::lowerStates() { SmallVector opsToLower; for (auto &op : *moduleOp.getBodyBlock()) - if (isa(&op)) + if (isa(&op)) opsToLower.push_back(&op); for (auto *op : opsToLower) { LLVM_DEBUG(llvm::dbgs() << "- Lowering " << *op << "\n"); - auto result = TypeSwitch(op) - .Case( - [&](auto op) { return lowerState(op); }) - .Default(success()); + auto result = + TypeSwitch(op) + .Case( + [&](auto op) { return lowerState(op); }) + .Default(success()); if (failed(result)) return failure(); } return success(); } -LogicalResult ModuleLowering::lowerState(StateOp stateOp) { - // We don't support arcs beyond latency 1 yet. These should be easy to add in - // the future though. - if (stateOp.getLatency() > 1) - return stateOp.emitError("state with latency > 1 not supported"); - - // Grab all operands from the state op and make it drop all its references. - // This allows `materializeValue` to move an operation if this state was the - // last user. - auto stateClock = stateOp.getClock(); - auto stateEnable = stateOp.getEnable(); - auto stateReset = stateOp.getReset(); - auto stateInputs = SmallVector(stateOp.getInputs()); +template +LogicalResult ModuleLowering::lowerStateLike( + Operation *stateOp, Value stateClock, Value stateEnable, Value stateReset, + ArrayRef stateInputs, FlatSymbolRefAttr callee) { + // Grab all operands from the state op at the callsite and make it drop all + // its references. This allows `materializeValue` to move an operation if this + // state was the last user. // Get the clock tree and enable condition for this state's clock. If this arc // carries an explicit enable condition, fold that into the enable provided by // the clock gates in the arc's clock tree. auto info = getOrCreateClockLowering(stateClock); info.enable = info.clock.getOrCreateAnd( - info.enable, info.clock.materializeValue(stateEnable), stateOp.getLoc()); + info.enable, info.clock.materializeValue(stateEnable), stateOp->getLoc()); // Allocate the necessary state within the model. SmallVector allocatedStates; - for (unsigned stateIdx = 0; stateIdx < stateOp.getNumResults(); ++stateIdx) { - auto type = stateOp.getResult(stateIdx).getType(); + for (unsigned stateIdx = 0; stateIdx < stateOp->getNumResults(); ++stateIdx) { + auto type = stateOp->getResult(stateIdx).getType(); auto intType = dyn_cast(type); if (!intType) - return stateOp.emitOpError("result ") + return stateOp->emitOpError("result ") << stateIdx << " has non-integer type " << type << "; only integer types are supported"; auto stateType = StateType::get(intType); - auto state = stateBuilder.create(stateOp.getLoc(), stateType, + auto state = stateBuilder.create(stateOp->getLoc(), stateType, storageArg); if (auto names = stateOp->getAttrOfType("names")) state->setAttr("name", names[stateIdx]); @@ -455,18 +456,18 @@ LogicalResult ModuleLowering::lowerState(StateOp stateOp) { OpBuilder nonResetBuilder = info.clock.builder; if (stateReset) { auto materializedReset = info.clock.materializeValue(stateReset); - auto ifOp = info.clock.builder.create(stateOp.getLoc(), + auto ifOp = info.clock.builder.create(stateOp->getLoc(), materializedReset, true); for (auto [alloc, resTy] : - llvm::zip(allocatedStates, stateOp.getResultTypes())) { + llvm::zip(allocatedStates, stateOp->getResultTypes())) { if (!isa(resTy)) stateOp->emitOpError("Non-integer result not supported yet!"); auto thenBuilder = ifOp.getThenBodyBuilder(); Value constZero = - thenBuilder.create(stateOp.getLoc(), resTy, 0); - thenBuilder.create(stateOp.getLoc(), alloc, constZero, + thenBuilder.create(stateOp->getLoc(), resTy, 0); + thenBuilder.create(stateOp->getLoc(), alloc, constZero, Value()); } @@ -475,24 +476,50 @@ LogicalResult ModuleLowering::lowerState(StateOp stateOp) { stateOp->dropAllReferences(); - auto newStateOp = nonResetBuilder.create( - stateOp.getLoc(), stateOp.getResultTypes(), stateOp.getArcAttr(), + auto newStateOp = nonResetBuilder.create( + stateOp->getLoc(), stateOp->getResultTypes(), callee, materializedOperands); // Create the write ops that write the result of the transfer function to the // allocated state storage. for (auto [alloc, result] : llvm::zip(allocatedStates, newStateOp.getResults())) - nonResetBuilder.create(stateOp.getLoc(), alloc, result, + nonResetBuilder.create(stateOp->getLoc(), alloc, result, info.enable); // Replace all uses of the arc with reads from the allocated state. - for (auto [alloc, result] : llvm::zip(allocatedStates, stateOp.getResults())) + for (auto [alloc, result] : llvm::zip(allocatedStates, stateOp->getResults())) replaceValueWithStateRead(result, alloc); - stateOp.erase(); + stateOp->erase(); return success(); } +LogicalResult ModuleLowering::lowerState(StateOp stateOp) { + // We don't support arcs beyond latency 1 yet. These should be easy to add in + // the future though. + if (stateOp.getLatency() > 1) + return stateOp.emitError("state with latency > 1 not supported"); + + auto stateInputs = SmallVector(stateOp.getInputs()); + + return lowerStateLike(stateOp, stateOp.getClock(), + stateOp.getEnable(), stateOp.getReset(), + stateInputs, stateOp.getArcAttr()); +} + +LogicalResult ModuleLowering::lowerState(sim::DPICallOp callOp) { + // Clocked call op can be considered as arc state with single latency. + auto stateClock = callOp.getClock(); + if (!stateClock) + return callOp.emitError("unclocked DPI call not implemented yet"); + + auto stateInputs = SmallVector(callOp.getInputs()); + + return lowerStateLike(callOp, stateClock, callOp.getEnable(), + Value(), stateInputs, + callOp.getCalleeAttr()); +} + LogicalResult ModuleLowering::lowerState(MemoryOp memOp) { auto allocMemOp = stateBuilder.create( memOp.getLoc(), memOp.getType(), storageArg, memOp->getAttrs()); diff --git a/lib/Dialect/Sim/CMakeLists.txt b/lib/Dialect/Sim/CMakeLists.txt index db239ddd2652..395e7216dfc8 100644 --- a/lib/Dialect/Sim/CMakeLists.txt +++ b/lib/Dialect/Sim/CMakeLists.txt @@ -29,6 +29,7 @@ add_circt_dialect_library(CIRCTSim CIRCTHW CIRCTSeq CIRCTSV + MLIRFuncDialect MLIRIR MLIRPass MLIRTransforms diff --git a/lib/Dialect/Sim/SimOps.cpp b/lib/Dialect/Sim/SimOps.cpp index 8d7c3c96c805..d0e304fa85be 100644 --- a/lib/Dialect/Sim/SimOps.cpp +++ b/lib/Dialect/Sim/SimOps.cpp @@ -13,6 +13,7 @@ #include "circt/Dialect/Sim/SimOps.h" #include "circt/Dialect/HW/ModuleImplementation.h" #include "circt/Dialect/SV/SVOps.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Interfaces/FunctionImplementation.h" @@ -69,12 +70,15 @@ ParseResult DPIFuncOp::parse(OpAsmParser &parser, OperationState &result) { LogicalResult sim::DPICallOp::verifySymbolUses(SymbolTableCollection &symbolTable) { - auto referencedOp = dyn_cast_or_null( - symbolTable.lookupNearestSymbolFrom(*this, getCalleeAttr())); + auto referencedOp = + symbolTable.lookupNearestSymbolFrom(*this, getCalleeAttr()); if (!referencedOp) return emitError("cannot find function declaration '") << getCallee() << "'"; - return success(); + if (isa(referencedOp)) + return success(); + return emitError("callee must be 'sim.dpi.func' or 'func.func' but got '") + << referencedOp->getName() << "'"; } void DPIFuncOp::print(OpAsmPrinter &p) { diff --git a/lib/Dialect/Sim/Transforms/CMakeLists.txt b/lib/Dialect/Sim/Transforms/CMakeLists.txt index 6802452cca59..d6caf2ddc26f 100644 --- a/lib/Dialect/Sim/Transforms/CMakeLists.txt +++ b/lib/Dialect/Sim/Transforms/CMakeLists.txt @@ -1,4 +1,5 @@ add_circt_dialect_library(CIRCTSimTransforms + LowerDPIFunc.cpp ProceduralizeSim.cpp @@ -12,8 +13,10 @@ add_circt_dialect_library(CIRCTSimTransforms CIRCTSV CIRCTComb CIRCTSupport + MLIRFuncDialect MLIRIR MLIRPass + MLIRLLVMDialect MLIRSCFDialect MLIRTransformUtils ) diff --git a/lib/Dialect/Sim/Transforms/LowerDPIFunc.cpp b/lib/Dialect/Sim/Transforms/LowerDPIFunc.cpp new file mode 100644 index 000000000000..dd65b2bcaa8a --- /dev/null +++ b/lib/Dialect/Sim/Transforms/LowerDPIFunc.cpp @@ -0,0 +1,177 @@ +//===- LowerDPIFunc.cpp - Lower sim.dpi.func to func.func ----*- 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 +// +//===----------------------------------------------------------------------=== +// +// This pass lowers Sim DPI func ops to MLIR func and call. +// +// sim.dpi.func @foo(input %a: i32, output %b: i64) +// hw.module @top (..) { +// %result = sim.dpi.call @foo(%a) clock %clock +// } +// +// -> +// +// func.func @foo(%a: i32, %b: !llvm.ptr) // Output is passed by a reference. +// func.func @foo_wrapper(%a: i32) -> (i64) { +// %0 = llvm.alloca: !llvm.ptr +// %v = func.call @foo (%a, %0) +// func.return %v +// } +// hw.module @mod(..) { +// %result = sim.dpi.call @foo_wrapper(%a) clock %clock +// } +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Sim/SimOps.h" +#include "circt/Dialect/Sim/SimPasses.h" +#include "circt/Support/Namespace.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" +#include "mlir/Dialect/LLVMIR/LLVMTypes.h" +#include "mlir/Transforms/DialectConversion.h" +#include "llvm/Support/Debug.h" + +#define DEBUG_TYPE "sim-lower-dpi-func" + +namespace circt { +namespace sim { +#define GEN_PASS_DEF_LOWERDPIFUNC +#include "circt/Dialect/Sim/SimPasses.h.inc" +} // namespace sim +} // namespace circt + +using namespace mlir; +using namespace circt; + +//===----------------------------------------------------------------------===// +// Pass Implementation +//===----------------------------------------------------------------------===// + +namespace { + +struct LoweringState { + DenseMap dpiFuncDeclMapping; + circt::Namespace nameSpace; +}; + +struct LowerDPIFuncPass : public sim::impl::LowerDPIFuncBase { + + LogicalResult lowerDPI(); + LogicalResult lowerDPIFuncOp(sim::DPIFuncOp simFunc, + LoweringState &loweringState, + SymbolTable &symbolTable); + void runOnOperation() override; +}; + +} // namespace + +LogicalResult LowerDPIFuncPass::lowerDPIFuncOp(sim::DPIFuncOp simFunc, + LoweringState &loweringState, + SymbolTable &symbolTable) { + ImplicitLocOpBuilder builder(simFunc.getLoc(), simFunc); + auto moduleType = simFunc.getModuleType(); + + llvm::SmallVector dpiFunctionArgumentTypes; + for (auto arg : moduleType.getPorts()) { + // TODO: Support a non-integer type. + if (!arg.type.isInteger()) + return simFunc->emitError() + << "non-integer type argument is unsupported now"; + + if (arg.dir == hw::ModulePort::Input) + dpiFunctionArgumentTypes.push_back(arg.type); + else + // Output must be passed by a reference. + dpiFunctionArgumentTypes.push_back( + LLVM::LLVMPointerType::get(arg.type.getContext())); + } + + auto funcType = builder.getFunctionType(dpiFunctionArgumentTypes, {}); + func::FuncOp func; + + // Look up func.func by verilog name since the function name is equal to the + // symbol name in MLIR + if (auto verilogName = simFunc.getVerilogName()) { + func = symbolTable.lookup(*verilogName); + // TODO: Check if function type matches. + } + + // If a referred function is not in the same module, create an external + // function declaration. + if (!func) { + func = builder.create(simFunc.getVerilogName() + ? *simFunc.getVerilogName() + : simFunc.getSymName(), + funcType); + // External function needs to be private. + func.setPrivate(); + } + + // Create a wrapper module that calls a DPI function. + auto funcOp = builder.create( + loweringState.nameSpace.newName(simFunc.getSymName() + "_wrapper"), + moduleType.getFuncType()); + + // Map old symbol to a new func op. + loweringState.dpiFuncDeclMapping[simFunc.getSymNameAttr()] = funcOp; + + builder.setInsertionPointToStart(funcOp.addEntryBlock()); + SmallVector functionInputs; + SmallVector functionOutputAllocas; + + size_t inputIndex = 0; + for (auto arg : moduleType.getPorts()) { + if (arg.dir == hw::ModulePort::InOut) + return funcOp->emitError() << "inout is currently not supported"; + + if (arg.dir == hw::ModulePort::Input) { + functionInputs.push_back(funcOp.getArgument(inputIndex)); + ++inputIndex; + } else { + // Allocate an output placeholder. + auto one = builder.create(builder.getI64IntegerAttr(1)); + auto alloca = builder.create( + builder.getType(), arg.type, one); + functionInputs.push_back(alloca); + functionOutputAllocas.push_back(alloca); + } + } + + builder.create(func, functionInputs); + + SmallVector results; + for (auto functionOutputAlloca : functionOutputAllocas) + results.push_back(builder.create( + functionOutputAlloca.getElemType(), functionOutputAlloca)); + + builder.create(results); + + simFunc.erase(); + return success(); +} + +LogicalResult LowerDPIFuncPass::lowerDPI() { + LLVM_DEBUG(llvm::dbgs() << "Lowering sim DPI func to func.func\n"); + auto op = getOperation(); + LoweringState state; + state.nameSpace.add(op); + auto &symbolTable = getAnalysis(); + for (auto simFunc : llvm::make_early_inc_range(op.getOps())) + if (failed(lowerDPIFuncOp(simFunc, state, symbolTable))) + return failure(); + + op.walk([&](sim::DPICallOp op) { + auto func = state.dpiFuncDeclMapping.at(op.getCalleeAttr().getAttr()); + op.setCallee(func.getSymNameAttr()); + }); + return success(); +} + +void LowerDPIFuncPass::runOnOperation() { + if (failed(lowerDPI())) + return signalPassFailure(); +} diff --git a/test/Dialect/Arc/lower-state.mlir b/test/Dialect/Arc/lower-state.mlir index 0538d1e4aae4..b312bb115c8d 100644 --- a/test/Dialect/Arc/lower-state.mlir +++ b/test/Dialect/Arc/lower-state.mlir @@ -353,3 +353,16 @@ hw.module @BlackBox(in %clk: !seq.clock) { } // CHECK-NOT: hw.module.extern private @BlackBoxExt hw.module.extern private @BlackBoxExt(in %a: i42, in %b: i42, out c: i42, out d: i42) + + +func.func private @func(%arg0: i32, %arg1: i32) -> i32 +// CHECK-LABEL: arc.model @adder +hw.module @adder(in %clock : i1, in %a : i32, in %b : i32, out c : i32) { + %0 = seq.to_clock %clock + %1 = sim.func.dpi.call @func(%a, %b) clock %0 : (i32, i32) -> i32 + // CHECK: arc.clock_tree + // CHECK-NEXT: %[[A:.+]] = arc.state_read %in_a : + // CHECK-NEXT: %[[B:.+]] = arc.state_read %in_b : + // CHECK-NEXT: %[[RESULT:.+]] = func.call @func(%6, %7) : (i32, i32) -> i32 + hw.output %1 : i32 +} diff --git a/test/Dialect/Sim/lower-dpi.mlir b/test/Dialect/Sim/lower-dpi.mlir new file mode 100644 index 000000000000..dc9211f126dd --- /dev/null +++ b/test/Dialect/Sim/lower-dpi.mlir @@ -0,0 +1,50 @@ +// RUN: circt-opt --sim-lower-dpi-func %s | FileCheck %s + +sim.func.dpi @foo(out arg0: i32, in %arg1: i32, out arg2: i32) +// CHECK-LABEL: func.func private @foo(!llvm.ptr, i32, !llvm.ptr) +// CHECK-LABEL: func.func @foo_wrapper(%arg0: i32) -> (i32, i32) { +// CHECK-NEXT: %0 = llvm.mlir.constant(1 : i64) : i64 +// CHECK-NEXT: %1 = llvm.alloca %0 x i32 : (i64) -> !llvm.ptr +// CHECK-NEXT: %2 = llvm.mlir.constant(1 : i64) : i64 +// CHECK-NEXT: %3 = llvm.alloca %2 x i32 : (i64) -> !llvm.ptr +// CHECK-NEXT: call @foo(%1, %arg0, %3) : (!llvm.ptr, i32, !llvm.ptr) -> () +// CHECK-NEXT: %4 = llvm.load %1 : !llvm.ptr -> i32 +// CHECK-NEXT: %5 = llvm.load %3 : !llvm.ptr -> i32 +// CHECK-NEXT: return %4, %5 : i32, i32 +// CHECK-NEXT: } + +// CHECK-LABEL: func.func @bar_wrapper(%arg0: i32) -> (i32, i32) { +// CHECK-NEXT: %0 = llvm.mlir.constant(1 : i64) : i64 +// CHECK-NEXT: %1 = llvm.alloca %0 x i32 : (i64) -> !llvm.ptr +// CHECK-NEXT: %2 = llvm.mlir.constant(1 : i64) : i64 +// CHECK-NEXT: %3 = llvm.alloca %2 x i32 : (i64) -> !llvm.ptr +// CHECK-NEXT: call @bar_c_name(%1, %arg0, %3) : (!llvm.ptr, i32, !llvm.ptr) -> () +// CHECK-NEXT: %4 = llvm.load %1 : !llvm.ptr -> i32 +// CHECK-NEXT: %5 = llvm.load %3 : !llvm.ptr -> i32 +// CHECK-NEXT: return %4, %5 : i32, i32 +// CHECK-NEXT: } +// CHECK-LABEL: func.func @bar_c_name + +sim.func.dpi @bar(out arg0: i32, in %arg1: i32, out arg2: i32) attributes {verilogName="bar_c_name"} +func.func @bar_c_name(%arg0: !llvm.ptr, %arg1: i32, %arg2: !llvm.ptr) { + func.return +} + +// CHECK-LABEL: func.func private @baz_c_name(!llvm.ptr, i32, !llvm.ptr) +// CHECK-LABEL: func.func @baz_wrapper(%arg0: i32) -> (i32, i32) +// CHECK: call @baz_c_name(%1, %arg0, %3) : (!llvm.ptr, i32, !llvm.ptr) -> () +sim.func.dpi @baz(out arg0: i32, in %arg1: i32, out arg2: i32) attributes {verilogName="baz_c_name"} + +// CHECK-LABEL: hw.module @dpi_call +hw.module @dpi_call(in %clock : !seq.clock, in %enable : i1, in %in: i32, + out o1: i32, out o2: i32, out o3: i32, out o4: i32, out o5: i32, out o6: i32) { + // CHECK-NEXT: %0:2 = sim.func.dpi.call @foo_wrapper(%in) clock %clock : (i32) -> (i32, i32) + // CHECK-NEXT: %1:2 = sim.func.dpi.call @bar_wrapper(%in) : (i32) -> (i32, i32) + // CHECK-NEXT: %2:2 = sim.func.dpi.call @baz_wrapper(%in) : (i32) -> (i32, i32) + // CHECK-NEXT: hw.output %0#0, %0#1, %1#0, %1#1, %2#0, %2#1 : i32, i32, i32, i32, i32, i32 + %0, %1 = sim.func.dpi.call @foo(%in) clock %clock : (i32) -> (i32, i32) + %2, %3 = sim.func.dpi.call @bar(%in) : (i32) -> (i32, i32) + %4, %5 = sim.func.dpi.call @baz(%in) : (i32) -> (i32, i32) + + hw.output %0, %1, %2, %3, %4, %5 : i32, i32, i32, i32, i32, i32 +} diff --git a/test/Dialect/Sim/round-trip.mlir b/test/Dialect/Sim/round-trip.mlir index a98b7242decf..bbbe07d4d931 100644 --- a/test/Dialect/Sim/round-trip.mlir +++ b/test/Dialect/Sim/round-trip.mlir @@ -19,14 +19,15 @@ hw.module @stop_finish(in %clock : !seq.clock, in %cond : i1) { // CHECK-LABEL: sim.func.dpi @dpi(out arg0 : i1, in %arg1 : i1, out arg2 : i1) sim.func.dpi @dpi(out arg0: i1, in %arg1: i1, out arg2: i1) +func.func private @func(%arg1: i1) -> (i1, i1) hw.module @dpi_call(in %clock : !seq.clock, in %enable : i1, in %in: i1) { // CHECK: sim.func.dpi.call @dpi(%in) clock %clock enable %enable : (i1) -> (i1, i1) %0, %1 = sim.func.dpi.call @dpi(%in) clock %clock enable %enable: (i1) -> (i1, i1) // CHECK: sim.func.dpi.call @dpi(%in) clock %clock : (i1) -> (i1, i1) %2, %3 = sim.func.dpi.call @dpi(%in) clock %clock : (i1) -> (i1, i1) - // CHECK: sim.func.dpi.call @dpi(%in) enable %enable : (i1) -> (i1, i1) - %4, %5 = sim.func.dpi.call @dpi(%in) enable %enable : (i1) -> (i1, i1) - // CHECK: sim.func.dpi.call @dpi(%in) : (i1) -> (i1, i1) - %6, %7 = sim.func.dpi.call @dpi(%in) : (i1) -> (i1, i1) + // CHECK: sim.func.dpi.call @func(%in) enable %enable : (i1) -> (i1, i1) + %4, %5 = sim.func.dpi.call @func(%in) enable %enable : (i1) -> (i1, i1) + // CHECK: sim.func.dpi.call @func(%in) : (i1) -> (i1, i1) + %6, %7 = sim.func.dpi.call @func(%in) : (i1) -> (i1, i1) } diff --git a/test/Dialect/Sim/sim-errors.mlir b/test/Dialect/Sim/sim-errors.mlir index 393636f0ce28..a70360b1cdfe 100644 --- a/test/Dialect/Sim/sim-errors.mlir +++ b/test/Dialect/Sim/sim-errors.mlir @@ -42,3 +42,12 @@ hw.module @proc_print_sv() { sim.proc.print %lit } } + +// ----- + +hw.module.extern @non_func(out arg0: i1, in %arg1: i1, out arg2: i1) + +hw.module @dpi_call(in %clock : !seq.clock, in %in: i1) { + // expected-error @below {{callee must be 'sim.dpi.func' or 'func.func' but got 'hw.module.extern'}} + %0, %1 = sim.func.dpi.call @non_func(%in) : (i1) -> (i1, i1) +} diff --git a/tools/arcilator/CMakeLists.txt b/tools/arcilator/CMakeLists.txt index 1257bc173107..7293e698aa9d 100644 --- a/tools/arcilator/CMakeLists.txt +++ b/tools/arcilator/CMakeLists.txt @@ -20,6 +20,7 @@ target_link_libraries(arcilator CIRCTOM CIRCTSeqToSV CIRCTSeqTransforms + CIRCTSimTransforms CIRCTSupport CIRCTTransforms MLIRBuiltinToLLVMIRTranslation diff --git a/tools/arcilator/arcilator.cpp b/tools/arcilator/arcilator.cpp index 1348e5a979cf..0c7f5c384d5b 100644 --- a/tools/arcilator/arcilator.cpp +++ b/tools/arcilator/arcilator.cpp @@ -22,6 +22,8 @@ #include "circt/Dialect/Emit/EmitDialect.h" #include "circt/Dialect/HW/HWPasses.h" #include "circt/Dialect/Seq/SeqPasses.h" +#include "circt/Dialect/Sim/SimDialect.h" +#include "circt/Dialect/Sim/SimPasses.h" #include "circt/InitAllDialects.h" #include "circt/InitAllPasses.h" #include "circt/Support/Passes.h" @@ -249,6 +251,7 @@ static void populateHwModuleToArcPipeline(PassManager &pm) { opts.tapMemories = observeMemories; pm.addPass(arc::createInferMemoriesPass(opts)); } + pm.addPass(sim::createLowerDPIFunc()); pm.addPass(createCSEPass()); pm.addPass(arc::createArcCanonicalizerPass()); @@ -567,6 +570,7 @@ static LogicalResult executeArcilator(MLIRContext &context) { mlir::scf::SCFDialect, om::OMDialect, seq::SeqDialect, + sim::SimDialect, sv::SVDialect >(); // clang-format on