Skip to content

Commit 5a1799e

Browse files
committed
Add more document for DPI and tweak trait name.
1 parent a1c35ad commit 5a1799e

File tree

3 files changed

+207
-4
lines changed

3 files changed

+207
-4
lines changed

docs/src/explanations/dpi.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
---
2+
layout: docs
3+
title: "Calling Native Functions from Chisel (DPI)"
4+
section: "chisel3"
5+
---
6+
7+
# Calling Native Functions from Chisel (DPI)
8+
9+
## DPI Basics
10+
11+
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.
12+
13+
Here's a simple example that demonstrates printing a message from a C++ function:
14+
```c++
15+
extern "C" void hello()
16+
{
17+
std::cout << "hello from c++\\n";
18+
}
19+
```
20+
21+
To call this function from Chisel, we need to define a corresponding DPI object.
22+
23+
```scala
24+
object Hello extends DPIVoidFunctionImport {
25+
override val functionName = "hello"
26+
override val clocked = true
27+
final def apply() = super.call()
28+
}
29+
30+
Hello() // Print
31+
```
32+
33+
Explanation:
34+
35+
* `Hello` inherits from `DPIVoidFunctionImport` because the C++ function doesn't return a value (void).
36+
* `functionName` specifies the C-linkage name of the C++ function.
37+
* `clocked = true` indicates that its function call is invoked at clock's posedge.
38+
* We recommend defining the apply method for a more Scala-like syntax.
39+
40+
## Type ABI
41+
42+
Unlike normal Chisel compilation flow, we use a specific ABI for types to interact with DPI.
43+
44+
### Argument Types
45+
46+
* Operand and result types must be passive.
47+
* A vector is lowered to an *unpacked* *open* array type, e.g., `a: Vec<4, UInt>` to `byte a []`.
48+
* A bundle is lowered to a packed struct.
49+
* Integer types are lowered into 2-state types.
50+
Small integer types (< 64 bit) must be compatible with C-types and arguments are passed by value. Users are required to use specific integer types for small integers shown in the table below. Large integers are lowered to bit and passed by reference.
51+
52+
53+
| Width | Verilog Type | Argument Passing Modes |
54+
| ----- | ------------ | ---------------------- |
55+
| 1 | bit | value |
56+
| 8 | byte | value |
57+
| 16 | shortint | value |
58+
| 32 | int | value |
59+
| 64 | longint | value |
60+
| > 64 | bit [w-1:0] | reference |
61+
62+
### Function Types
63+
The type of DPI object you need depends on the characteristics of your C++ function:
64+
65+
* Return Type
66+
* For functions that don't return a value (like hello), use `DPIVoidFunctionImport`.
67+
* For functions with a return type (e.g., integer addition), use `DPINonVoidFunctionImport[T]`, where `T` is the return type.
68+
* The output argument must be the last argument in DPI fuction.
69+
* Clocked vs. Unclocked:
70+
* Clocked: The function call is evaluated at the associated clock's positive edge. By default, Chisel uses the current module's clock. For custom clocks, use `withClocked(clock) {..}`. If the function has a return value, it will be available in the next clock cycle.
71+
* Unclocked: The function call is evaluated immediately.
72+
73+
## Example: Adding Two Numbers
74+
Here's an example of a DPI function that calculates the sum of two numbers:
75+
76+
```c++
77+
extern "C" void add(int lhs, int rhs, int* result)
78+
{
79+
*result = lhs + rhs;
80+
}
81+
```
82+
83+
```scala
84+
object Add extends DPINonVoidFunctionImport[UInt] {
85+
override val functionName = "add"
86+
override val ret = UInt(32.W)
87+
override val clocked = false
88+
override val inputNames = Some(Seq("lhs", "rhs"))
89+
override val outputName = Some("result")
90+
final def apply(lhs: UInt, rhs: UInt): UInt = super.call(lhs, rhs)
91+
}
92+
93+
val result = Add(2.U(32.W), 3.U(32.W))
94+
```
95+
96+
Explanation:
97+
98+
* `Add` inherits from `DPINonVoidFunctionImport[UInt]` because it returns a 32-bit unsigned integer.
99+
* `ret` specifies the return type.
100+
* `clocked` indicates that this is a clocked function call.
101+
* `inputNames` and `outputName` provide optional names for the function's arguments and return value (these are just for Verilog readability).
102+
103+
## Example: Sum of an array
104+
Chisel vectors are converted into SystemVerilog open arrays when used with DPI. Since memory layout can vary between simulators, it's recommended to use `svSize` and `svGetBitArrElemVecVal` to access array elements.
105+
106+
```c++
107+
extern "C" void sum(const svOpenArrayHandle array, int* result) {
108+
// Get a length of the open array.
109+
int size = svSize(array, 1);
110+
// Initialize the result value.
111+
*result = 0;
112+
for(size_t i = 0; i < size; ++i) {
113+
svBitVecVal vec;
114+
svGetBitArrElemVecVal(&vec, array, i);
115+
*result += vec;
116+
}
117+
}
118+
```
119+
120+
```scala
121+
object Sum extends DPINonVoidFunctionImport[UInt] {
122+
override val functionName = "sum"
123+
override val ret = UInt(32.W)
124+
override val clocked = false
125+
override val inputNames = Some(Seq("array"))
126+
override val outputName = Some("result")
127+
final def apply(array: Vec[UInt]): UInt = super.call(array)
128+
}
129+
130+
val io = IO(new Bundle {
131+
val a = Input(Vec(3, UInt(32.W)))
132+
val b = Input(Vec(6, UInt(32.W)))
133+
})
134+
135+
val sum_a = Sum(io.a) // compute a[0] + a[1] + a[2]
136+
val sum_b = Sum(io.b) // compute b[0] + ... + b[5]
137+
```
138+
139+
# FAQ
140+
141+
* Can we export functions? -- No, not currently. Consider using a black box for such functionality.
142+
* Can we call a DPI function in initial block? -- No, not currently. Consider using a black box for initialization.
143+
* Can we call two clocked DPI calls and pass the result to another within the same clock? -- No, not currently. Please merge the DPI functions into a single function.

src/main/scala/chisel3/util/circt/DPI.scala

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,25 @@ trait DPIFunctionImport {
119119
def inputNames: Option[Seq[String]] = None
120120
}
121121

122-
// Base trait for a non-void function that returns `T`.
123122
trait DPINonVoidFunctionImport[T <: Data] extends DPIFunctionImport {
123+
124+
/** Base trait for a non-void function that returns `T`.
125+
*
126+
* @tparam T Return type
127+
* @see Please refer [[https://www.chisel-lang.org/docs/explanations/dpi]] for more detail.
128+
* @example {{{
129+
* object Add extends DPINonVoidFunctionImport[UInt] {
130+
* override val functionName = "add"
131+
* override val ret = UInt(32.W)
132+
* override val clocked = false
133+
* override val inputNames = Some(Seq("lhs", "rhs"))
134+
* override val outputName = Some("result")
135+
* final def apply(lhs: UInt, rhs: UInt): UInt = super.call(lhs, rhs)
136+
* }
137+
*
138+
* Add(a, b) // call a native `add` function.
139+
* }}}
140+
*/
124141
def ret: T
125142
def clocked: Boolean
126143
def outputName: Option[String] = None
@@ -133,8 +150,22 @@ trait DPINonVoidFunctionImport[T <: Data] extends DPIFunctionImport {
133150
final def call(data: Data*): T = callWithEnable(true.B, data: _*)
134151
}
135152

136-
// Base trait for a clocked void function.
137-
trait DPIClockedVoidFunctionImport extends DPIFunctionImport {
153+
trait DPIVoidFunctionImport extends DPIFunctionImport {
154+
155+
/** Base trait for a void function.
156+
*
157+
* @see Please refer [[https://www.chisel-lang.org/docs/explanations/dpi]] for more detail.
158+
* @example {{{
159+
* object Hello extends DPIVoidFunctionImport {
160+
* override val functionName = "hello"
161+
* override val clocked = true
162+
* final def apply() = super.call()
163+
* }
164+
*
165+
* Hello() // call a native `hello` function.
166+
* }}}
167+
*/
168+
def clocked: Boolean
138169
final def callWithEnable(enable: Bool, data: Data*): Unit =
139170
RawClockedVoidFunctionCall(functionName, inputNames)(Module.clock, enable, data: _*)
140171
final def call(data: Data*): Unit = callWithEnable(true.B, data: _*)

src/test/scala/chiselTests/DPISpec.scala

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ private object EmitDPIImplementation {
1717
val dpiImpl = s"""
1818
|#include <stdint.h>
1919
|#include <iostream>
20+
|#include <svdpi.h>
2021
|
2122
|extern "C" void hello()
2223
|{
@@ -27,6 +28,16 @@ private object EmitDPIImplementation {
2728
|{
2829
| *result = lhs + rhs;
2930
|}
31+
|
32+
|extern "C" void sum(const svOpenArrayHandle array, int* result) {
33+
| int size = svSize(array, 1);
34+
| *result = 0;
35+
| for(size_t i = 0; i < size; ++i) {
36+
| svBitVecVal vec;
37+
| svGetBitArrElemVecVal(&vec, array, i);
38+
| *result += vec;
39+
| }
40+
|}
3041
""".stripMargin
3142

3243
class DummyDPI extends BlackBox with HasBlackBoxInline {
@@ -62,8 +73,9 @@ class DPIIntrinsicTest extends Module {
6273
io.add_unclocked_result := result_unclocked
6374
}
6475

65-
object Hello extends DPIClockedVoidFunctionImport {
76+
object Hello extends DPIVoidFunctionImport {
6677
override val functionName = "hello"
78+
override val clocked = true
6779
final def apply() = super.call()
6880
}
6981

@@ -85,22 +97,35 @@ object AddUnclocked extends DPINonVoidFunctionImport[UInt] {
8597
final def apply(lhs: UInt, rhs: UInt): UInt = super.call(lhs, rhs)
8698
}
8799

100+
object Sum extends DPINonVoidFunctionImport[UInt] {
101+
override val functionName = "sum"
102+
override val ret = UInt(32.W)
103+
override val clocked = false
104+
override val inputNames = Some(Seq("array"))
105+
override val outputName = Some("result")
106+
final def apply(array: Vec[UInt]): UInt = super.call(array)
107+
}
108+
88109
class DPIAPITest extends Module {
89110
val io = IO(new Bundle {
90111
val a = Input(UInt(32.W))
91112
val b = Input(UInt(32.W))
92113
val add_clocked_result = Output(UInt(32.W))
93114
val add_unclocked_result = Output(UInt(32.W))
115+
val sum_result = Output(UInt(32.W))
94116
})
95117

96118
EmitDPIImplementation()
97119

98120
Hello()
121+
99122
val result_clocked = AddClocked(io.a, io.b)
100123
val result_unclocked = AddUnclocked(io.a, io.b)
124+
val result_sum = Sum(VecInit(Seq(io.a, io.b, io.a)))
101125

102126
io.add_clocked_result := result_clocked
103127
io.add_unclocked_result := result_unclocked
128+
io.sum_result := result_sum
104129
}
105130

106131
class DPISpec extends AnyFunSpec with Matchers {
@@ -151,6 +176,8 @@ class DPISpec extends AnyFunSpec with Matchers {
151176
dpi.io.b.poke(36.U)
152177
dpi.io.add_unclocked_result.peek()
153178
dpi.io.add_unclocked_result.expect(60)
179+
dpi.io.sum_result.peek()
180+
dpi.io.sum_result.expect(84)
154181

155182
dpi.clock.step()
156183
dpi.io.a.poke(24.U)
@@ -159,6 +186,8 @@ class DPISpec extends AnyFunSpec with Matchers {
159186
dpi.io.add_clocked_result.expect(60)
160187
dpi.io.add_unclocked_result.peek()
161188
dpi.io.add_unclocked_result.expect(36)
189+
dpi.io.sum_result.peek()
190+
dpi.io.sum_result.expect(60)
162191
}
163192
.result
164193

0 commit comments

Comments
 (0)