Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIRRTL] Add DPI call intrinsic and lowering pass #7139

Merged
merged 11 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
28 changes: 28 additions & 0 deletions docs/Dialects/FIRRTL/FIRRTLIntrinsics.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,31 @@ ifdef USE_FORMAL_ONLY_CONSTRAINTS
`endif // USE_UNR_ONLY_CONSTRAINTS
endif // USE_FORMAL_ONLY_CONSTRAINTS
```

### circt_dpi_call

Call a DPI function. `clock` is optional and if `clock` is not provided,
the callee is invoked when input values are changed.
If provided, the dpi function is called at clock's posedge. The result values behave
like registers and the DPI function is used as a state transfer function of them.

`enable` operand is used to conditionally call the DPI since DPI call could be quite
more expensive than native constructs. When `enable` is low, results of unclocked
calls are undefined and evaluated into `X`. Users are expected to gate result values
by another `enable` to model a default value of results.

For clocked calls, a low enable means that its register state transfer function is
not called. Hence their values will not be modify in that clock.

| Parameter | Type | Description |
| ------------- | ------ | -------------------------------- |
| isClocked | int | Set 1 if the dpi call is clocked |
| functionName | string | Specify the function name |


| Port | Direction | Type | Description |
| ----------------- | --------- | -------- | ------------------------------- |
| clock (optional) | input | Clock | Optional clock operand |
| enable | input | UInt<1> | Enable signal |
Copy link
Member Author

Choose a reason for hiding this comment

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

enable is not optional at Chisel level to follow the same design as verification intrinsic.

| ... | input | Signals | Arguments to DPI function call |
| result (optional) | output | Signal | Optional result of the dpi call |
1 change: 1 addition & 0 deletions include/circt/Conversion/ExportVerilog.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
namespace circt {
namespace hw {
class HWModuleLike;
class HWEmittableModuleLike;
} // namespace hw

std::unique_ptr<mlir::Pass>
Expand Down
5 changes: 3 additions & 2 deletions include/circt/Conversion/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ def HWLowerInstanceChoices : Pass<"hw-lower-instance-choices",
];
}

def PrepareForEmission : Pass<"prepare-for-emission",
"hw::HWModuleOp"> {
def PrepareForEmission : InterfacePass<"prepare-for-emission",
"hw::HWEmittableModuleLike"> {
let summary = "Prepare IR for ExportVerilog";
let description = [{
This pass runs only PrepareForEmission.
Expand Down Expand Up @@ -808,6 +808,7 @@ def LowerSimToSV: Pass<"lower-sim-to-sv", "mlir::ModuleOp"> {
let constructor = "circt::createLowerSimToSVPass()";
let dependentDialects = [
"circt::comb::CombDialect",
"circt::emit::EmitDialect",
"circt::sv::SVDialect",
"circt::hw::HWDialect"
];
Expand Down
9 changes: 0 additions & 9 deletions include/circt/Dialect/Emit/EmitOpInterfaces.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,6 @@
#include "circt/Support/LLVM.h"
#include "mlir/IR/OpDefinition.h"

namespace circt {
namespace emit {

template <typename ConcreteType>
class Emittable : public OpTrait::TraitBase<ConcreteType, Emittable> {};

} // namespace emit
} // namespace circt

#include "circt/Dialect/Emit/EmitOpInterfaces.h.inc"

#endif // CIRCT_DIALECT_EMIT_EMITOPINTERFACES_H
2 changes: 1 addition & 1 deletion include/circt/Dialect/Emit/EmitOpInterfaces.td
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

include "mlir/IR/OpBase.td"

def Emittable : NativeOpTrait<"Emittable"> {
def Emittable : OpInterface<"Emittable", []> {
let cppNamespace = "::circt::emit";
}

Expand Down
19 changes: 19 additions & 0 deletions include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.td
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,23 @@ def HasBeenResetIntrinsicOp : FIRRTLOp<"int.has_been_reset", [Pure]> {
}


def DPICallIntrinsicOp : FIRRTLOp<"int.dpi.call",
[AttrSizedOperandSegments]> {
let summary = "Import and call DPI function";
let description = [{
The `int.dpi.call` intrinsic calls an external function.
See Sim dialect DPI call op.
}];

let arguments = (ins StrAttr:$functionName,
Optional<NonConstClockType>:$clock,
Optional<NonConstUInt1Type>:$enable,
Variadic<PassiveType>:$inputs);
let results = (outs Optional<PassiveType>:$result);
let assemblyFormat = [{
$functionName `(` $inputs `)` (`clock` $clock^)? (`enable` $enable^)?
attr-dict `:` functional-type($inputs, results)
}];
}

#endif // CIRCT_DIALECT_FIRRTL_FIRRTLINTRINSICS_TD
2 changes: 2 additions & 0 deletions include/circt/Dialect/FIRRTL/Passes.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ std::unique_ptr<mlir::Pass> createCreateCompanionAssume();

std::unique_ptr<mlir::Pass> createModuleSummaryPass();

std::unique_ptr<mlir::Pass> createLowerDPIPass();

/// Generate the code for registering passes.
#define GEN_PASS_REGISTRATION
#include "circt/Dialect/FIRRTL/Passes.h.inc"
Expand Down
6 changes: 6 additions & 0 deletions include/circt/Dialect/FIRRTL/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -908,4 +908,10 @@ def ModuleSummary :
let constructor = "circt::firrtl::createModuleSummaryPass()";
}

def LowerDPI : Pass<"firrtl-lower-dpi", "firrtl::CircuitOp"> {
let summary = "Lower DPI intrinsic into Sim DPI operations";
let constructor = "circt::firrtl::createLowerDPIPass()";
let dependentDialects = ["hw::HWDialect", "seq::SeqDialect", "sim::SimDialect"];
}

#endif // CIRCT_DIALECT_FIRRTL_PASSES_TD
1 change: 1 addition & 0 deletions include/circt/Dialect/HW/HWOpInterfaces.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#ifndef CIRCT_DIALECT_HW_HWOPINTERFACES_H
#define CIRCT_DIALECT_HW_HWOPINTERFACES_H

#include "circt/Dialect/Emit/EmitOpInterfaces.h"
#include "circt/Dialect/HW/HWInstanceImplementation.h"
#include "circt/Dialect/HW/HWTypes.h"
#include "circt/Dialect/HW/InnerSymbolTable.h"
Expand Down
10 changes: 10 additions & 0 deletions include/circt/Dialect/HW/HWOpInterfaces.td
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

include "mlir/IR/SymbolInterfaces.td"
include "mlir/IR/OpBase.td"
include "circt/Dialect/Emit/EmitOpInterfaces.td"
include "circt/Support/InstanceGraphInterface.td"

def PortList : OpInterface<"PortList", []> {
Expand Down Expand Up @@ -402,6 +403,15 @@ def HWMutableModuleLike : OpInterface<"HWMutableModuleLike", [HWModuleLike]> {
];
}

def HWEmittableModuleLike : OpInterface<"HWEmittableModuleLike", [HWModuleLike,
Emittable]> {
let cppNamespace = "::circt::hw";
let description = [{
This interface indicates that the module like op is emitatble in SV and
requires to be ran SV legalization on its body.
}];
}


def HWInstanceLike : OpInterface<"HWInstanceLike", [
InstanceGraphInstanceOpInterface]> {
Expand Down
2 changes: 1 addition & 1 deletion include/circt/Dialect/HW/HWStructure.td
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class HWModuleOpBase<string mnemonic, list<Trait> traits = []> :
def HWModuleOp : HWModuleOpBase<"module",
[IsolatedFromAbove, RegionKindInterface,
SingleBlockImplicitTerminator<"OutputOp">,
Emittable]>{
HWEmittableModuleLike]>{
let summary = "HW Module";
let description = [{
The "hw.module" operation represents a Verilog module, including a given
Expand Down
3 changes: 2 additions & 1 deletion include/circt/Dialect/SV/SVStatements.td
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,8 @@ def FuncOp : SVOp<"func",
[IsolatedFromAbove, Symbol, OpAsmOpInterface, ProceduralRegion,
DeclareOpInterfaceMethods<HWModuleLike>,
DeclareOpInterfaceMethods<PortList>,
FunctionOpInterface, HasParent<"mlir::ModuleOp">]> {
FunctionOpInterface, HasParent<"mlir::ModuleOp">,
HWEmittableModuleLike]> {
let summary = "A SystemVerilog function";
let description = [{
`sv.func` represents SystemVerilog function in IEEE 1800-2017 section 13.4
Expand Down
1 change: 1 addition & 0 deletions include/circt/Dialect/Sim/SimDialect.td
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def SimDialect : Dialect {

let useDefaultTypePrinterParser = 0;
let useDefaultAttributePrinterParser = 0;
let dependentDialects = ["circt::hw::HWDialect"];
}

#endif // CIRCT_DIALECT_SIM_SIMDIALECT
3 changes: 3 additions & 0 deletions include/circt/Dialect/Sim/SimOps.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
#include "mlir/IR/OpImplementation.h"
#include "mlir/IR/SymbolTable.h"

#include "circt/Dialect/HW/HWOpInterfaces.h"
#include "circt/Dialect/Seq/SeqDialect.h"
#include "circt/Dialect/Seq/SeqTypes.h"
#include "circt/Dialect/Sim/SimDialect.h"
#include "circt/Support/BuilderUtils.h"
#include "mlir/Interfaces/CallInterfaces.h"
#include "mlir/Interfaces/FunctionInterfaces.h"

#define GET_OP_CLASSES
#include "circt/Dialect/Sim/Sim.h.inc"
Expand Down
92 changes: 92 additions & 0 deletions include/circt/Dialect/Sim/SimOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
#define CIRCT_DIALECT_SIM_SIMOPS_TD

include "mlir/Interfaces/SideEffectInterfaces.td"
include "mlir/Interfaces/FunctionInterfaces.td"
include "circt/Dialect/Sim/SimDialect.td"
include "circt/Dialect/Seq/SeqTypes.td"
include "circt/Dialect/HW/HWOpInterfaces.td"
include "circt/Dialect/HW/HWTypes.td"

class SimOp<string mnemonic, list<Trait> traits = []> :
Op<SimDialect, mnemonic, traits>;
Expand Down Expand Up @@ -54,4 +57,93 @@ def FatalOp : SimOp<"fatal"> {
let assemblyFormat = "$clk `,` $cond attr-dict";
}

def DPIFuncOp : SimOp<"func.dpi",
[IsolatedFromAbove, Symbol, OpAsmOpInterface,
FunctionOpInterface]> {
Copy link
Member Author

Choose a reason for hiding this comment

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

This is similar change to 09df7f5. It was necessary to put DPI func op into circuit op to make the lowering composable.

let summary = "A System Verilog function";
let description = [{
`sim.func.dpi` models an external function in a core dialect.
}];

let arguments = (ins
SymbolNameAttr:$sym_name,
TypeAttrOf<ModuleType>:$module_type,
OptionalAttr<DictArrayAttr>:$per_argument_attrs,
OptionalAttr<LocationArrayAttr>:$argument_locs,
OptionalAttr<StrAttr>:$verilogName
);
let regions = (region AnyRegion:$body);

let hasCustomAssemblyFormat = 1;

let extraClassDeclaration = [{
mlir::FunctionType getFunctionType() {
return getModuleType().getFuncType();
}

void setFunctionTypeAttr(mlir::TypeAttr mlirType) {
setModuleType(cast<hw::ModuleType>(mlirType.getValue()));
}

/// Returns the argument types of this function.
ArrayRef<Type> getArgumentTypes() { return getFunctionType().getInputs(); }

/// Returns the result types of this function.
ArrayRef<Type> getResultTypes() { return getFunctionType().getResults(); }

::mlir::Region *getCallableRegion() { return nullptr; }
}];
}

def DPICallOp : SimOp<"func.dpi.call",
[CallOpInterface, AttrSizedOperandSegments,
DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
let summary = "A call option for DPI function with an optional clock and enable";
let description = [{
`sim.func.dpi.call` represents SystemVerilog DPI function call. There are two
optional operands `clock` and `enable`.

If `clock` is not provided, the callee is invoked when input values are changed.
If provided, the dpi function is called at clock's posedge. The result values behave
like registers and the DPI function is used as a state transfer function of them.

`enable` operand is used to conditionally call the DPI since DPI call could be quite
more expensive than native constructs. When `enable` is low, results of unclocked
calls are undefined and in SV results they are lowered into `X`. Users are expected
to gate result values by another `enable` to model a default value of results.

For clocked calls, a low enable means that its register state transfer function is
not called. Hence their values will not be modify in that clock.
}];

let arguments = (ins FlatSymbolRefAttr:$callee,
Optional<ClockType>:$clock,
Optional<I1>:$enable,
Variadic<AnyType>:$inputs);
let results = (outs Variadic<AnyType>);

let assemblyFormat = [{
$callee `(` $inputs `)` (`clock` $clock^)? (`enable` $enable^)?
attr-dict `:` functional-type($inputs, results)
}];

let extraClassDeclaration = [{
operand_range getArgOperands() {
return getInputs();
}
MutableOperandRange getArgOperandsMutable() {
return getInputsMutable();
}
mlir::CallInterfaceCallable getCallableForCallee() {
return (*this)->getAttrOfType<mlir::SymbolRefAttr>("callee");
}

/// Set the callee for this operation.
void setCalleeFromCallable(mlir::CallInterfaceCallable callee) {
(*this)->setAttr(getCalleeAttrName(), callee.get<mlir::SymbolRefAttr>());
}
}];

}

#endif // CIRCT_DIALECT_SIM_SIMOPS_TD
9 changes: 9 additions & 0 deletions include/circt/Support/Namespace.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include "circt/Support/LLVM.h"
#include "circt/Support/SymCache.h"
#include "mlir/IR/BuiltinOps.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/ADT/Twine.h"
Expand All @@ -42,6 +43,14 @@ class Namespace {
return *this;
}

void add(mlir::ModuleOp module) {
assert(module->getNumRegions() == 1);
for (auto &op : module.getBody(0)->getOperations())
if (auto symbol = op.getAttrOfType<mlir::StringAttr>(
SymbolTable::getSymbolAttrName()))
nextIndex.insert({symbol.getValue(), 0});
}

/// SymbolCache initializer; initialize from every key that is convertible to
/// a StringAttr in the SymbolCache.
void add(SymbolCache &symCache) {
Expand Down
Loading
Loading