[FIRRTL] Add input and output names to DPI intrinsic (#7265)

Fix https://github.com/llvm/circt/issues/7226. This adds support for specifying input and output names. This uses `;` separated string list following the same design as `guard` parameter of assert intrinsic.
This commit is contained in:
Hideto Ueno 2024-07-03 14:16:51 +09:00 committed by GitHub
parent 2ec4a1cd62
commit 0bcfbdc599
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 66 additions and 17 deletions

View File

@ -230,10 +230,12 @@ by another `enable` to model a default value of results.
For clocked calls, a low enable means that its register state transfer function is 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. not called. Hence their values will not be modify in that clock.
| Parameter | Type | Description | | Parameter | Type | Description |
| ------------- | ------ | -------------------------------- | | ------------- | ------ | --------------------------------------------------- |
| isClocked | int | Set 1 if the dpi call is clocked | | isClocked | int | Set 1 if the dpi call is clocked. |
| functionName | string | Specify the function name | | functionName | string | Specify the function name. |
| inputNames | string | Semicolon-delimited list of input names. Optional. |
| outputName | string | Output name. Optional. |
| Port | Direction | Type | Description | | Port | Direction | Type | Description |

View File

@ -214,6 +214,8 @@ def DPICallIntrinsicOp : FIRRTLOp<"int.dpi.call",
}]; }];
let arguments = (ins StrAttr:$functionName, let arguments = (ins StrAttr:$functionName,
OptionalAttr<StrArrayAttr>:$inputNames,
OptionalAttr<StrAttr>:$outputName,
Optional<NonConstClockType>:$clock, Optional<NonConstClockType>:$clock,
Optional<NonConstUInt1Type>:$enable, Optional<NonConstUInt1Type>:$enable,
Variadic<PassiveType>:$inputs); Variadic<PassiveType>:$inputs);

View File

@ -665,8 +665,10 @@ public:
using IntrinsicConverter::IntrinsicConverter; using IntrinsicConverter::IntrinsicConverter;
bool check(GenericIntrinsic gi) override { bool check(GenericIntrinsic gi) override {
if (gi.hasNParam(2) || gi.namedIntParam("isClocked") || if (gi.hasNParam(2, 2) || gi.namedIntParam("isClocked") ||
gi.namedParam("functionName")) gi.namedParam("functionName") ||
gi.namedParam("inputNames", /*optional=*/true) ||
gi.namedParam("outputName", /*optional=*/true))
return true; return true;
auto isClocked = getIsClocked(gi); auto isClocked = getIsClocked(gi);
// If clocked, the first operand must be a clock. // If clocked, the first operand must be a clock.
@ -683,6 +685,14 @@ public:
PatternRewriter &rewriter) override { PatternRewriter &rewriter) override {
auto isClocked = getIsClocked(gi); auto isClocked = getIsClocked(gi);
auto functionName = gi.getParamValue<StringAttr>("functionName"); auto functionName = gi.getParamValue<StringAttr>("functionName");
ArrayAttr inputNamesStrArray;
StringAttr outputStr = gi.getParamValue<StringAttr>("outputName");
if (auto inputNames = gi.getParamValue<StringAttr>("inputNames")) {
SmallVector<StringRef> inputNamesTemporary;
inputNames.strref().split(inputNamesTemporary, ';', /*MaxSplit=*/-1,
/*KeepEmpty=*/false);
inputNamesStrArray = rewriter.getStrArrayAttr(inputNamesTemporary);
}
// Clock and enable are optional. // Clock and enable are optional.
Value clock = isClocked ? adaptor.getOperands()[0] : Value(); Value clock = isClocked ? adaptor.getOperands()[0] : Value();
Value enable = adaptor.getOperands()[static_cast<size_t>(isClocked)]; Value enable = adaptor.getOperands()[static_cast<size_t>(isClocked)];
@ -691,7 +701,8 @@ public:
adaptor.getOperands().drop_front(static_cast<size_t>(isClocked) + 1); adaptor.getOperands().drop_front(static_cast<size_t>(isClocked) + 1);
rewriter.replaceOpWithNewOp<DPICallIntrinsicOp>( rewriter.replaceOpWithNewOp<DPICallIntrinsicOp>(
gi.op, gi.op.getResultTypes(), functionName, clock, enable, inputs); gi.op, gi.op.getResultTypes(), functionName, inputNamesStrArray,
outputStr, clock, enable, inputs);
} }
}; };

View File

@ -5535,6 +5535,16 @@ static bool isTypeAllowedForDPI(Operation *op, Type type) {
} }
LogicalResult DPICallIntrinsicOp::verify() { LogicalResult DPICallIntrinsicOp::verify() {
if (auto inputNames = getInputNames()) {
if (getInputs().size() != inputNames->size())
return emitError() << "inputNames has " << inputNames->size()
<< " elements but there are " << getInputs().size()
<< " input arguments";
}
if (auto outputName = getOutputName())
if (getNumResults() == 0)
return emitError() << "output name is given but there is no result";
auto checkType = [this](Type type) { auto checkType = [this](Type type) {
return isTypeAllowedForDPI(*this, type); return isTypeAllowedForDPI(*this, type);
}; };

View File

@ -175,6 +175,9 @@ sim::DPIFuncOp LowerDPI::getOrCreateDPIFuncDecl(DPICallIntrinsicOp op) {
builder.setInsertionPointToStart(circuitOp.getBodyBlock()); builder.setInsertionPointToStart(circuitOp.getBodyBlock());
auto inputTypes = op.getInputs().getTypes(); auto inputTypes = op.getInputs().getTypes();
auto outputTypes = op.getResultTypes(); auto outputTypes = op.getResultTypes();
ArrayAttr inputNames = op.getInputNamesAttr();
StringAttr outputName = op.getOutputNameAttr();
assert(outputTypes.size() <= 1);
SmallVector<hw::ModulePort> ports; SmallVector<hw::ModulePort> ports;
ports.reserve(inputTypes.size() + outputTypes.size()); ports.reserve(inputTypes.size() + outputTypes.size());
@ -183,7 +186,8 @@ sim::DPIFuncOp LowerDPI::getOrCreateDPIFuncDecl(DPICallIntrinsicOp op) {
for (auto [idx, inType] : llvm::enumerate(inputTypes)) { for (auto [idx, inType] : llvm::enumerate(inputTypes)) {
hw::ModulePort port; hw::ModulePort port;
port.dir = hw::ModulePort::Direction::Input; port.dir = hw::ModulePort::Direction::Input;
port.name = builder.getStringAttr(Twine("in_") + Twine(idx)); port.name = inputNames ? cast<StringAttr>(inputNames[idx])
: builder.getStringAttr(Twine("in_") + Twine(idx));
port.type = lowerType(inType); port.type = lowerType(inType);
ports.push_back(port); ports.push_back(port);
} }
@ -192,7 +196,8 @@ sim::DPIFuncOp LowerDPI::getOrCreateDPIFuncDecl(DPICallIntrinsicOp op) {
for (auto [idx, outType] : llvm::enumerate(outputTypes)) { for (auto [idx, outType] : llvm::enumerate(outputTypes)) {
hw::ModulePort port; hw::ModulePort port;
port.dir = hw::ModulePort::Direction::Output; port.dir = hw::ModulePort::Direction::Output;
port.name = builder.getStringAttr(Twine("out_") + Twine(idx)); port.name = outputName ? outputName
: builder.getStringAttr(Twine("out_") + Twine(idx));
port.type = lowerType(outType); port.type = lowerType(outType);
ports.push_back(port); ports.push_back(port);
} }

View File

@ -2481,6 +2481,25 @@ firrtl.circuit "DPI" {
} }
} }
// -----
firrtl.circuit "DPI" {
firrtl.module @DPI(in %clock : !firrtl.clock, in %enable : !firrtl.uint<1>, in %in_0: !firrtl.uint<8>, in %in_1: !firrtl.uint) {
// expected-error @below {{inputNames has 0 elements but there are 1 input arguments}}
%0 = firrtl.int.dpi.call "clocked_result"(%in_0) clock %clock enable %enable {inputNames=[]} : (!firrtl.uint<8>) -> !firrtl.uint<8>
}
}
// -----
firrtl.circuit "DPI" {
firrtl.module @DPI(in %clock : !firrtl.clock, in %enable : !firrtl.uint<1>, in %in_0: !firrtl.uint<8>, in %in_1: !firrtl.uint) {
// expected-error @below {{output name is given but there is no result}}
firrtl.int.dpi.call "clocked_result"(%in_0) clock %clock enable %enable {outputName="foo"} : (!firrtl.uint<8>) -> ()
}
}
// ----- // -----
firrtl.circuit "LHSTypes" { firrtl.circuit "LHSTypes" {

View File

@ -4,7 +4,7 @@
firrtl.circuit "DPI" { firrtl.circuit "DPI" {
// CHECK-NEXT: sim.func.dpi private @unclocked_result(in %in_0 : i8, in %in_1 : i8, out out_0 : i8) attributes {verilogName = "unclocked_result"} // CHECK-NEXT: sim.func.dpi private @unclocked_result(in %in_0 : i8, in %in_1 : i8, out out_0 : i8) attributes {verilogName = "unclocked_result"}
// CHECK-NEXT: sim.func.dpi private @clocked_void(in %in_0 : i8, in %in_1 : i8) attributes {verilogName = "clocked_void"} // CHECK-NEXT: sim.func.dpi private @clocked_void(in %in_0 : i8, in %in_1 : i8) attributes {verilogName = "clocked_void"}
// CHECK-NEXT: sim.func.dpi private @clocked_result(in %in_0 : i8, in %in_1 : i8, out out_0 : i8) attributes {verilogName = "clocked_result"} // CHECK-NEXT: sim.func.dpi private @clocked_result(in %foo : i8, in %bar : i8, out baz : i8) attributes {verilogName = "clocked_result"}
// CHECK-LABEL: firrtl.module @DPI // CHECK-LABEL: firrtl.module @DPI
firrtl.module @DPI(in %clock: !firrtl.clock, in %enable: !firrtl.uint<1>, in %in_0: !firrtl.uint<8>, in %in_1: !firrtl.uint<8>, out %out_0: !firrtl.uint<8>, out %out_1: !firrtl.uint<8>) attributes {convention = #firrtl<convention scalarized>} { firrtl.module @DPI(in %clock: !firrtl.clock, in %enable: !firrtl.uint<1>, in %in_0: !firrtl.uint<8>, in %in_1: !firrtl.uint<8>, out %out_0: !firrtl.uint<8>, out %out_1: !firrtl.uint<8>) attributes {convention = #firrtl<convention scalarized>} {
// CHECK-NEXT: %0 = builtin.unrealized_conversion_cast %clock : !firrtl.clock to !seq.clock // CHECK-NEXT: %0 = builtin.unrealized_conversion_cast %clock : !firrtl.clock to !seq.clock
@ -25,7 +25,7 @@ firrtl.circuit "DPI" {
// CHECK-NEXT: %14 = builtin.unrealized_conversion_cast %13 : i8 to !firrtl.uint<8> // CHECK-NEXT: %14 = builtin.unrealized_conversion_cast %13 : i8 to !firrtl.uint<8>
// CHECK-NEXT: firrtl.matchingconnect %out_0, %5 : !firrtl.uint<8> // CHECK-NEXT: firrtl.matchingconnect %out_0, %5 : !firrtl.uint<8>
// CHECK-NEXT: firrtl.matchingconnect %out_1, %14 : !firrtl.uint<8> // CHECK-NEXT: firrtl.matchingconnect %out_1, %14 : !firrtl.uint<8>
%0 = firrtl.int.dpi.call "clocked_result"(%in_0, %in_1) clock %clock enable %enable {name = "result1"} : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<8> %0 = firrtl.int.dpi.call "clocked_result"(%in_0, %in_1) clock %clock enable %enable {inputNames = ["foo", "bar"], outputName = "baz"} : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<8>
firrtl.int.dpi.call "clocked_void"(%in_0, %in_1) clock %clock enable %enable : (!firrtl.uint<8>, !firrtl.uint<8>) -> () firrtl.int.dpi.call "clocked_void"(%in_0, %in_1) clock %clock enable %enable : (!firrtl.uint<8>, !firrtl.uint<8>) -> ()
%1 = firrtl.int.dpi.call "unclocked_result"(%in_0, %in_1) enable %enable {name = "result2"} : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<8> %1 = firrtl.int.dpi.call "unclocked_result"(%in_0, %in_1) enable %enable {name = "result2"} : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<8>
firrtl.matchingconnect %out_0, %0 : !firrtl.uint<8> firrtl.matchingconnect %out_0, %0 : !firrtl.uint<8>

View File

@ -157,8 +157,8 @@ firrtl.circuit "Foo" {
// CHECK-LABEL: firrtl.module private @DPIIntrinsicTest(in %clock: !firrtl.clock, in %enable: !firrtl.uint<1>, in %in1: !firrtl.uint<8>, in %in2: !firrtl.uint<8>) // CHECK-LABEL: firrtl.module private @DPIIntrinsicTest(in %clock: !firrtl.clock, in %enable: !firrtl.uint<1>, in %in1: !firrtl.uint<8>, in %in2: !firrtl.uint<8>)
firrtl.module private @DPIIntrinsicTest(in %clock : !firrtl.clock, in %enable : !firrtl.uint<1>, in %in1: !firrtl.uint<8>, in %in2: !firrtl.uint<8>) { firrtl.module private @DPIIntrinsicTest(in %clock : !firrtl.clock, in %enable : !firrtl.uint<1>, in %in1: !firrtl.uint<8>, in %in2: !firrtl.uint<8>) {
// CHECK-NEXT: %0 = firrtl.int.dpi.call "clocked_result"(%in1, %in2) clock %clock enable %enable : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<8> // CHECK-NEXT: %0 = firrtl.int.dpi.call "clocked_result"(%in1, %in2) clock %clock enable %enable {inputNames = ["foo", "bar"], outputName = "baz"} : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<8>
%0 = firrtl.int.generic "circt_dpi_call" <isClocked: ui32 = 1, functionName: none = "clocked_result"> %clock, %enable, %in1, %in2 : (!firrtl.clock, !firrtl.uint<1>, !firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<8> %0 = firrtl.int.generic "circt_dpi_call" <isClocked: ui32 = 1, functionName: none = "clocked_result", inputNames: none = "foo;bar", outputName: none = "baz"> %clock, %enable, %in1, %in2 : (!firrtl.clock, !firrtl.uint<1>, !firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<8>
// CHECK-NEXT: firrtl.int.dpi.call "clocked_void"(%in1, %in2) clock %clock enable %enable : (!firrtl.uint<8>, !firrtl.uint<8>) -> () // CHECK-NEXT: firrtl.int.dpi.call "clocked_void"(%in1, %in2) clock %clock enable %enable : (!firrtl.uint<8>, !firrtl.uint<8>) -> ()
firrtl.int.generic "circt_dpi_call" <isClocked: ui32 = 1, functionName: none = "clocked_void"> %clock, %enable, %in1, %in2 : (!firrtl.clock, !firrtl.uint<1>, !firrtl.uint<8>, !firrtl.uint<8>) -> () firrtl.int.generic "circt_dpi_call" <isClocked: ui32 = 1, functionName: none = "clocked_void"> %clock, %enable, %in1, %in2 : (!firrtl.clock, !firrtl.uint<1>, !firrtl.uint<8>, !firrtl.uint<8>) -> ()
// CHECK-NEXT: %1 = firrtl.int.dpi.call "unclocked_result"(%in1, %in2) enable %enable : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<8> // CHECK-NEXT: %1 = firrtl.int.dpi.call "unclocked_result"(%in1, %in2) enable %enable : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<8>

View File

@ -3,9 +3,9 @@
FIRRTL version 4.0.0 FIRRTL version 4.0.0
circuit DPI: circuit DPI:
; CHECK-LABEL: import "DPI-C" function void clocked_result( ; CHECK-LABEL: import "DPI-C" function void clocked_result(
; CHECK-NEXT: input byte in_0, ; CHECK-NEXT: input byte foo,
; CHECK-NEXT: in_1, ; CHECK-NEXT: bar,
; CHECK-NEXT: output byte out_0 ; CHECK-NEXT: output byte baz
; CHECK-NEXT: ); ; CHECK-NEXT: );
; CHECK-LABEL: import "DPI-C" function void clocked_void( ; CHECK-LABEL: import "DPI-C" function void clocked_void(
@ -46,7 +46,7 @@ circuit DPI:
input in: UInt<8>[2] input in: UInt<8>[2]
output out : UInt<8>[2] output out : UInt<8>[2]
node result1 = intrinsic(circt_dpi_call<isClocked = 1, functionName="clocked_result"> : UInt<8>, clock, enable, in[0], in[1]) node result1 = intrinsic(circt_dpi_call<isClocked = 1, functionName="clocked_result", inputNames="foo;bar", outputName="baz"> : UInt<8>, clock, enable, in[0], in[1])
intrinsic(circt_dpi_call<isClocked = 1, functionName="clocked_void">, clock, enable, in[0], in[1]) intrinsic(circt_dpi_call<isClocked = 1, functionName="clocked_void">, clock, enable, in[0], in[1])
node result2 = intrinsic(circt_dpi_call<isClocked = 0, functionName="unclocked_result"> : UInt<8>, enable, in[0], in[1]) node result2 = intrinsic(circt_dpi_call<isClocked = 0, functionName="unclocked_result"> : UInt<8>, enable, in[0], in[1])