-
Notifications
You must be signed in to change notification settings - Fork 647
Enhance test and document for DPI and tweak trait name. #4380
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,143 @@ | ||||||||||
| --- | ||||||||||
| layout: docs | ||||||||||
| title: "Calling Native Functions from Chisel (DPI)" | ||||||||||
| section: "chisel3" | ||||||||||
| --- | ||||||||||
|
|
||||||||||
| # Calling Native Functions from Chisel (DPI) | ||||||||||
|
|
||||||||||
| ## DPI Basics | ||||||||||
|
|
||||||||||
| DPI API allows you to integrate native code into your Chisel hardware designs. This enables you to leverage existing libraries or implement functionality that is difficult to express directly in Chisel. | ||||||||||
uenoku marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||
|
|
||||||||||
| Here's a simple example that demonstrates printing a message from a C++ function: | ||||||||||
| ```c++ | ||||||||||
| extern "C" void hello() | ||||||||||
| { | ||||||||||
| std::cout << "hello from c++\\n"; | ||||||||||
| } | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| To call this function from Chisel, we need to define a corresponding DPI object. | ||||||||||
|
|
||||||||||
| ```scala | ||||||||||
|
||||||||||
| object Hello extends DPIVoidFunctionImport { | ||||||||||
| override val functionName = "hello" | ||||||||||
| override val clocked = true | ||||||||||
| final def apply() = super.call() | ||||||||||
| } | ||||||||||
|
|
||||||||||
| Hello() // Print | ||||||||||
|
||||||||||
| ``` | ||||||||||
|
|
||||||||||
| Explanation: | ||||||||||
|
|
||||||||||
| * `Hello` inherits from `DPIVoidFunctionImport` because the C++ function doesn't return a value (void). | ||||||||||
uenoku marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||
| * `functionName` specifies the C-linkage name of the C++ function. | ||||||||||
| * `clocked = true` indicates that its function call is invoked at clock's posedge. | ||||||||||
uenoku marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||
| * We recommend defining the apply method for a more Scala-like syntax. | ||||||||||
uenoku marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||
|
|
||||||||||
| ## Type ABI | ||||||||||
|
|
||||||||||
| Unlike normal Chisel compilation flow, we use a specific ABI for types to interact with DPI. | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what this means. Could you elaborate?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It may also be useful to explicitly point out that what we're doing in this section is showing how Chisel types map to C types. |
||||||||||
|
|
||||||||||
| ### Argument Types | ||||||||||
|
|
||||||||||
| * Operand and result types must be passive. | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we have another document that explains what "passive" means, we should link to it here. This is a rather technical term, and will otherwise cause confusion. |
||||||||||
| * A vector is lowered to an *unpacked* *open* array type, e.g., `a: Vec<4, UInt>` to `byte a []`. | ||||||||||
| * A bundle is lowered to a packed struct. | ||||||||||
|
||||||||||
| * A vector is lowered to an *unpacked* *open* array type, e.g., `a: Vec<4, UInt>` to `byte a []`. | |
| * A bundle is lowered to a packed struct. | |
| * A `Vec` is lowered to an *unpacked* *open* array type, e.g., `a: Vec<4, UInt>` to `byte a []`. | |
| * A `Bundle` is lowered to a packed struct. |
for vec i think you mean a chisel3.Vec not a scala Vector, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, chisel3.Vec.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * Integer types are lowered into 2-state types. | |
| * `Int`, `SInt`, `Clock`, `Reset` types are lowered into 2-state types. |
? Are clock and reset lowered?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not actually sure if you are talkign about Chisel Data subclasses or not
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type of DPI object I need for what?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading ahead, I think this section would be better typed out in prose, rather than summarized as a bulleted list.
"There are several base classes which are used to define DPI functions in Chisel. Which one you use depends on two factors: the return type of the function in C++ and how the DPI is called in relation to the clock."
Then outline the information in the bullet points in two paragraphs after that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It surprised me to see here that the int* result is an out-parameter. Is that always the case? (If so, maybe it needs to be called out earlier. Or maybe I just missed it?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I thought I clarified that but apparently it was dropped somewhere. I'll add explanation for that.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again would be helpful to see this with mdoc, I assume this Add function belongs inside a Module or a RawModule given that we say clocked is False
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but this is set to false so isn't NOT a clocked function call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is svOpenArrayHandle?
I guess, more generally, what libraries does Chisel depend on in the C++ DPI? We can't simply assume that a general Chisel user will have whatever library exposes DPI.h or whatever it is.
It would be good to have a section at the very start that gives the C++ prerequisites.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the suggestion, that makese sense. SV Spec (Section 35) defineds svdpi.h which declares these functions and types (including svOpenArrayHandle). These are expected to be implemented by simulators. I'll create a section for that.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again please use mdoc to check for compile if not chisel elaboration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should clocked be true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For OpenArray example both clocked and unclocked work. So I didn't explicitly describe the example. It depends on whether user wants to evaluate the value at every input value changes or clock's posedge.
This is an example for Add
chisel/src/test/scala/chiselTests/DPISpec.scala
Lines 158 to 161 in ead5fc9
| dpi.io.add_clocked_result.peek() | |
| dpi.io.add_clocked_result.expect(60) | |
| dpi.io.add_unclocked_result.peek() | |
| dpi.io.add_unclocked_result.expect(36) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you show an example where having clocked = false makes sense? Is that done in a when context...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unclocked calls be useful when user wants to replace pure (but expensive) combinatorial logics with dpi call (actually Add is a good example of such use case).
An unclocked call under when(cond) is lowered into always_comb + if(cond) and DPI is conditionally invoked as well. I updated Add example to include both of them.
class AddTest extends Module {
val io = IO(new Bundle {
val a = Input(UInt(32.W))
val b = Input(UInt(32.W))
val c = Output(UInt(32.W))
val d = Output(UInt(32.W))
val en = Input(Bool())
})
// Call DPI only when `en` is true.
when (io.en) {
io.c := AddClocked(io.a, io.b)
io.d := AddUnclocked(io.a, io.b)
} .otherwise {
io.c := 0.U(32.W)
io.d := 0.U(32.W)
}
}module AddTest(
input clock,
reset,
input [31:0] io_a,
io_b,
output [31:0] io_c,
io_d,
input io_en
);
logic [31:0] _add_0;
reg [31:0] _GEN;
always @(posedge clock) begin
if (io_en) begin
add(io_a, io_b, _add_0);
_GEN <= _add_0;
end
end // always @(posedge)
reg [31:0] _GEN_0;
always_comb begin
if (io_en) begin
add(io_a, io_b, _GEN_0);
end
else
_GEN_0 = 32'bx;
end // always_comb
assign io_c = io_en ? _GEN : 32'h0;
assign io_d = io_en ? _GEN_0 : 32'h0;There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by "export" here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what this last point means.
It feels like what we actually want is a light explanation of the underlying "framework" for the DPI calls, and that the above comment is actually a restriction imposed on this framework.
For example, is this true?: "All DPI calls on the same clock are dispatched at the same time. Because of this, it is not possible to use the result of one DPI call as an argument to another."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All DPI calls on the same clock are dispatched at the same time. Because of this, it is not possible to use the result of one DPI call as an argument to another
Yes, that's correct. This is fundamental restrictions for side-effecting operations in Chisel but DPI is a first example that has both side-effect and results.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ private object EmitDPIImplementation { | |
| val dpiImpl = s""" | ||
| |#include <stdint.h> | ||
| |#include <iostream> | ||
| |#include <svdpi.h> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To reiterate an earlier point: Let's add a prerequisites section near the top. Let's make sure to name what library(ies?) are needed for Chisel DPI, and let's be sure to include the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's also link to the documentation so that Chisel users can quickly cross-reference the DPI primitives, such as |
||
| | | ||
| |extern "C" void hello() | ||
| |{ | ||
|
|
@@ -27,6 +28,16 @@ private object EmitDPIImplementation { | |
| |{ | ||
| | *result = lhs + rhs; | ||
| |} | ||
| | | ||
| |extern "C" void sum(const svOpenArrayHandle array, int* result) { | ||
| | int size = svSize(array, 1); | ||
| | *result = 0; | ||
| | for(size_t i = 0; i < size; ++i) { | ||
| | svBitVecVal vec; | ||
| | svGetBitArrElemVecVal(&vec, array, i); | ||
| | *result += vec; | ||
| | } | ||
| |} | ||
| """.stripMargin | ||
|
|
||
| class DummyDPI extends BlackBox with HasBlackBoxInline { | ||
|
|
@@ -62,8 +73,9 @@ class DPIIntrinsicTest extends Module { | |
| io.add_unclocked_result := result_unclocked | ||
| } | ||
|
|
||
| object Hello extends DPIClockedVoidFunctionImport { | ||
| object Hello extends DPIVoidFunctionImport { | ||
| override val functionName = "hello" | ||
| override val clocked = true | ||
| final def apply() = super.call() | ||
| } | ||
|
|
||
|
|
@@ -85,22 +97,35 @@ object AddUnclocked extends DPINonVoidFunctionImport[UInt] { | |
| final def apply(lhs: UInt, rhs: UInt): UInt = super.call(lhs, rhs) | ||
| } | ||
|
|
||
| object Sum extends DPINonVoidFunctionImport[UInt] { | ||
| override val functionName = "sum" | ||
| override val ret = UInt(32.W) | ||
| override val clocked = false | ||
| override val inputNames = Some(Seq("array")) | ||
| override val outputName = Some("result") | ||
| final def apply(array: Vec[UInt]): UInt = super.call(array) | ||
| } | ||
|
|
||
| class DPIAPITest extends Module { | ||
| val io = IO(new Bundle { | ||
| val a = Input(UInt(32.W)) | ||
| val b = Input(UInt(32.W)) | ||
| val add_clocked_result = Output(UInt(32.W)) | ||
| val add_unclocked_result = Output(UInt(32.W)) | ||
| val sum_result = Output(UInt(32.W)) | ||
| }) | ||
|
|
||
| EmitDPIImplementation() | ||
|
|
||
| Hello() | ||
|
|
||
| val result_clocked = AddClocked(io.a, io.b) | ||
| val result_unclocked = AddUnclocked(io.a, io.b) | ||
| val result_sum = Sum(VecInit(Seq(io.a, io.b, io.a))) | ||
|
|
||
| io.add_clocked_result := result_clocked | ||
| io.add_unclocked_result := result_unclocked | ||
| io.sum_result := result_sum | ||
| } | ||
|
|
||
| class DPISpec extends AnyFunSpec with Matchers { | ||
|
|
@@ -151,6 +176,8 @@ class DPISpec extends AnyFunSpec with Matchers { | |
| dpi.io.b.poke(36.U) | ||
| dpi.io.add_unclocked_result.peek() | ||
| dpi.io.add_unclocked_result.expect(60) | ||
| dpi.io.sum_result.peek() | ||
| dpi.io.sum_result.expect(84) | ||
|
|
||
| dpi.clock.step() | ||
| dpi.io.a.poke(24.U) | ||
|
|
@@ -159,6 +186,8 @@ class DPISpec extends AnyFunSpec with Matchers { | |
| dpi.io.add_clocked_result.expect(60) | ||
| dpi.io.add_unclocked_result.peek() | ||
| dpi.io.add_unclocked_result.expect(36) | ||
| dpi.io.sum_result.peek() | ||
| dpi.io.sum_result.expect(60) | ||
| } | ||
| .result | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.