esiaccel 0.2.2__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of esiaccel might be problematic. Click here for more details.
- esiaccel/CosimBackend.dll +0 -0
- esiaccel/CosimBackend.lib +0 -0
- esiaccel/CosimRpc.dll +0 -0
- esiaccel/CosimRpc.lib +0 -0
- esiaccel/ESICppRuntime.dll +0 -0
- esiaccel/ESICppRuntime.lib +0 -0
- esiaccel/EsiCosimDpiServer.dll +0 -0
- esiaccel/EsiCosimDpiServer.lib +0 -0
- esiaccel/MtiPli.dll +0 -0
- esiaccel/MtiPli.lib +0 -0
- esiaccel/__init__.py +31 -0
- esiaccel/abseil_dll.dll +0 -0
- esiaccel/accelerator.py +134 -0
- esiaccel/cares.dll +0 -0
- esiaccel/cmake/esiaccelConfig.cmake +49 -0
- esiaccel/codegen.py +197 -0
- esiaccel/cosim/Cosim_DpiPkg.sv +85 -0
- esiaccel/cosim/Cosim_Endpoint.sv +218 -0
- esiaccel/cosim/Cosim_Manifest.sv +32 -0
- esiaccel/cosim/driver.cpp +131 -0
- esiaccel/cosim/driver.sv +74 -0
- esiaccel/cosim/questa.py +141 -0
- esiaccel/cosim/simulator.py +382 -0
- esiaccel/cosim/verilator.py +92 -0
- esiaccel/esi-cosim.py +104 -0
- esiaccel/esiCppAccel.cp312-win_amd64.pyd +0 -0
- esiaccel/esiquery.exe +0 -0
- esiaccel/include/esi/Accelerator.h +219 -0
- esiaccel/include/esi/CLI.h +77 -0
- esiaccel/include/esi/Common.h +182 -0
- esiaccel/include/esi/Context.h +82 -0
- esiaccel/include/esi/Design.h +132 -0
- esiaccel/include/esi/Engines.h +124 -0
- esiaccel/include/esi/Logging.h +231 -0
- esiaccel/include/esi/Manifest.h +70 -0
- esiaccel/include/esi/Ports.h +482 -0
- esiaccel/include/esi/Services.h +453 -0
- esiaccel/include/esi/Types.h +334 -0
- esiaccel/include/esi/Utils.h +102 -0
- esiaccel/include/esi/Values.h +313 -0
- esiaccel/include/esi/backends/Cosim.h +78 -0
- esiaccel/include/esi/backends/RpcClient.h +97 -0
- esiaccel/include/esi/backends/RpcServer.h +73 -0
- esiaccel/include/esi/backends/Trace.h +87 -0
- esiaccel/libcrypto-3-x64.dll +0 -0
- esiaccel/libprotobuf.dll +0 -0
- esiaccel/libssl-3-x64.dll +0 -0
- esiaccel/re2.dll +0 -0
- esiaccel/types.py +565 -0
- esiaccel/utils.py +54 -0
- esiaccel/zlib1.dll +0 -0
- esiaccel-0.2.2.dist-info/METADATA +254 -0
- esiaccel-0.2.2.dist-info/RECORD +57 -0
- esiaccel-0.2.2.dist-info/WHEEL +5 -0
- esiaccel-0.2.2.dist-info/entry_points.txt +4 -0
- esiaccel-0.2.2.dist-info/licenses/LICENSE +234 -0
- esiaccel-0.2.2.dist-info/top_level.txt +1 -0
esiaccel/types.py
ADDED
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
# ===-----------------------------------------------------------------------===#
|
|
2
|
+
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
3
|
+
# See https://llvm.org/LICENSE.txt for license information.
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
5
|
+
# ===-----------------------------------------------------------------------===#
|
|
6
|
+
#
|
|
7
|
+
# The structure of the Python classes and hierarchy roughly mirrors the C++
|
|
8
|
+
# side, but wraps the C++ objects. The wrapper classes sometimes add convenience
|
|
9
|
+
# functionality and serve to return wrapped versions of the returned objects.
|
|
10
|
+
#
|
|
11
|
+
# ===-----------------------------------------------------------------------===#
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from . import esiCppAccel as cpp
|
|
16
|
+
|
|
17
|
+
from typing import TYPE_CHECKING
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from .accelerator import HWModule
|
|
21
|
+
|
|
22
|
+
from concurrent.futures import Future
|
|
23
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union
|
|
24
|
+
import sys
|
|
25
|
+
import traceback
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _get_esi_type(cpp_type: cpp.Type):
|
|
29
|
+
"""Get the wrapper class for a C++ type."""
|
|
30
|
+
if isinstance(cpp_type, cpp.ChannelType):
|
|
31
|
+
return _get_esi_type(cpp_type.inner)
|
|
32
|
+
|
|
33
|
+
for cpp_type_cls, wrapper_cls in __esi_mapping.items():
|
|
34
|
+
if isinstance(cpp_type, cpp_type_cls):
|
|
35
|
+
return wrapper_cls.wrap_cpp(cpp_type)
|
|
36
|
+
return ESIType.wrap_cpp(cpp_type)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Mapping from C++ types to wrapper classes
|
|
40
|
+
__esi_mapping: Dict[Type, Type] = {}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ESIType:
|
|
44
|
+
|
|
45
|
+
def __init__(self, id: str):
|
|
46
|
+
self._init_from_cpp(cpp.Type(id))
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def wrap_cpp(cls, cpp_type: cpp.Type):
|
|
50
|
+
"""Wrap a C++ ESI type with its corresponding Python ESI Type."""
|
|
51
|
+
instance = cls.__new__(cls)
|
|
52
|
+
instance._init_from_cpp(cpp_type)
|
|
53
|
+
return instance
|
|
54
|
+
|
|
55
|
+
def _init_from_cpp(self, cpp_type: cpp.Type):
|
|
56
|
+
"""Initialize instance attributes from a C++ type object."""
|
|
57
|
+
self.cpp_type = cpp_type
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def supports_host(self) -> Tuple[bool, Optional[str]]:
|
|
61
|
+
"""Does this type support host communication via Python? Returns either
|
|
62
|
+
'(True, None)' if it is, or '(False, reason)' if it is not."""
|
|
63
|
+
|
|
64
|
+
if self.bit_width % 8 != 0:
|
|
65
|
+
return (False, "runtime only supports types with multiple of 8 bits")
|
|
66
|
+
return (True, None)
|
|
67
|
+
|
|
68
|
+
def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
|
|
69
|
+
"""Is a Python object compatible with HW type? Returns either '(True,
|
|
70
|
+
None)' if it is, or '(False, reason)' if it is not."""
|
|
71
|
+
assert False, "unimplemented"
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def bit_width(self) -> int:
|
|
75
|
+
"""Size of this type, in bits. Negative for unbounded types."""
|
|
76
|
+
assert False, "unimplemented"
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def max_size(self) -> int:
|
|
80
|
+
"""Maximum size of a value of this type, in bytes."""
|
|
81
|
+
bitwidth = int((self.bit_width + 7) / 8)
|
|
82
|
+
if bitwidth < 0:
|
|
83
|
+
return bitwidth
|
|
84
|
+
return bitwidth
|
|
85
|
+
|
|
86
|
+
def serialize(self, obj) -> bytearray:
|
|
87
|
+
"""Convert a Python object to a bytearray."""
|
|
88
|
+
assert False, "unimplemented"
|
|
89
|
+
|
|
90
|
+
def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
|
|
91
|
+
"""Convert a bytearray to a Python object. Return the object and the
|
|
92
|
+
leftover bytes."""
|
|
93
|
+
assert False, "unimplemented"
|
|
94
|
+
|
|
95
|
+
def __str__(self) -> str:
|
|
96
|
+
return str(self.cpp_type)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class VoidType(ESIType):
|
|
100
|
+
|
|
101
|
+
def __init__(self, id: str):
|
|
102
|
+
self._init_from_cpp(cpp.VoidType(id))
|
|
103
|
+
|
|
104
|
+
def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
|
|
105
|
+
if obj is not None:
|
|
106
|
+
return (False, f"void type cannot must represented by None, not {obj}")
|
|
107
|
+
return (True, None)
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def bit_width(self) -> int:
|
|
111
|
+
return 8
|
|
112
|
+
|
|
113
|
+
def serialize(self, obj) -> bytearray:
|
|
114
|
+
# By convention, void is represented by a single byte of value 0.
|
|
115
|
+
return bytearray([0])
|
|
116
|
+
|
|
117
|
+
def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
|
|
118
|
+
if len(data) == 0:
|
|
119
|
+
raise ValueError(f"void type cannot be represented by {data}")
|
|
120
|
+
return (None, data[1:])
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
__esi_mapping[cpp.VoidType] = VoidType
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class BitsType(ESIType):
|
|
127
|
+
|
|
128
|
+
def __init__(self, id: str, width: int):
|
|
129
|
+
self._init_from_cpp(cpp.BitsType(id, width))
|
|
130
|
+
|
|
131
|
+
def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
|
|
132
|
+
if not isinstance(obj, (bytearray, bytes, list)):
|
|
133
|
+
return (False, f"invalid type: {type(obj)}")
|
|
134
|
+
if isinstance(obj, list) and not all(
|
|
135
|
+
[isinstance(b, int) and b.bit_length() <= 8 for b in obj]):
|
|
136
|
+
return (False, f"list item too large: {obj}")
|
|
137
|
+
if len(obj) != self.max_size:
|
|
138
|
+
return (False, f"wrong size: {len(obj)}")
|
|
139
|
+
return (True, None)
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def bit_width(self) -> int:
|
|
143
|
+
return self.cpp_type.width
|
|
144
|
+
|
|
145
|
+
def serialize(self, obj: Union[bytearray, bytes, List[int]]) -> bytearray:
|
|
146
|
+
if isinstance(obj, bytearray):
|
|
147
|
+
return obj
|
|
148
|
+
if isinstance(obj, bytes) or isinstance(obj, list):
|
|
149
|
+
return bytearray(obj)
|
|
150
|
+
raise ValueError(f"cannot convert {obj} to bytearray")
|
|
151
|
+
|
|
152
|
+
def deserialize(self, data: bytearray) -> Tuple[bytearray, bytearray]:
|
|
153
|
+
return (data[0:self.max_size], data[self.max_size:])
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
__esi_mapping[cpp.BitsType] = BitsType
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class IntType(ESIType):
|
|
160
|
+
|
|
161
|
+
def __init__(self, id: str, width: int):
|
|
162
|
+
self._init_from_cpp(cpp.IntegerType(id, width))
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def bit_width(self) -> int:
|
|
166
|
+
return self.cpp_type.width
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class UIntType(IntType):
|
|
170
|
+
|
|
171
|
+
def __init__(self, id: str, width: int):
|
|
172
|
+
self._init_from_cpp(cpp.UIntType(id, width))
|
|
173
|
+
|
|
174
|
+
def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
|
|
175
|
+
if not isinstance(obj, int):
|
|
176
|
+
return (False, f"must be an int, not {type(obj)}")
|
|
177
|
+
if obj < 0 or obj.bit_length() > self.bit_width:
|
|
178
|
+
return (False, f"out of range: {obj}")
|
|
179
|
+
return (True, None)
|
|
180
|
+
|
|
181
|
+
def __str__(self) -> str:
|
|
182
|
+
return f"uint{self.bit_width}"
|
|
183
|
+
|
|
184
|
+
def serialize(self, obj: int) -> bytearray:
|
|
185
|
+
return bytearray(int.to_bytes(obj, self.max_size, "little"))
|
|
186
|
+
|
|
187
|
+
def deserialize(self, data: bytearray) -> Tuple[int, bytearray]:
|
|
188
|
+
return (int.from_bytes(data[0:self.max_size],
|
|
189
|
+
"little"), data[self.max_size:])
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
__esi_mapping[cpp.UIntType] = UIntType
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class SIntType(IntType):
|
|
196
|
+
|
|
197
|
+
def __init__(self, id: str, width: int):
|
|
198
|
+
self._init_from_cpp(cpp.SIntType(id, width))
|
|
199
|
+
|
|
200
|
+
def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
|
|
201
|
+
if not isinstance(obj, int):
|
|
202
|
+
return (False, f"must be an int, not {type(obj)}")
|
|
203
|
+
if obj < 0:
|
|
204
|
+
if (-1 * obj) > 2**(self.bit_width - 1):
|
|
205
|
+
return (False, f"out of range: {obj}")
|
|
206
|
+
elif obj < 0:
|
|
207
|
+
if obj >= 2**(self.bit_width - 1) - 1:
|
|
208
|
+
return (False, f"out of range: {obj}")
|
|
209
|
+
return (True, None)
|
|
210
|
+
|
|
211
|
+
def __str__(self) -> str:
|
|
212
|
+
return f"sint{self.bit_width}"
|
|
213
|
+
|
|
214
|
+
def serialize(self, obj: int) -> bytearray:
|
|
215
|
+
return bytearray(int.to_bytes(obj, self.max_size, "little", signed=True))
|
|
216
|
+
|
|
217
|
+
def deserialize(self, data: bytearray) -> Tuple[int, bytearray]:
|
|
218
|
+
return (int.from_bytes(data[0:self.max_size], "little",
|
|
219
|
+
signed=True), data[self.max_size:])
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
__esi_mapping[cpp.SIntType] = SIntType
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class StructType(ESIType):
|
|
226
|
+
|
|
227
|
+
def __init__(self, id: str, fields: List[Tuple[str, "ESIType"]]):
|
|
228
|
+
# Convert Python ESIType fields to cpp Type fields
|
|
229
|
+
cpp_fields = [(name, field_type.cpp_type) for name, field_type in fields]
|
|
230
|
+
self._init_from_cpp(cpp.StructType(id, cpp_fields))
|
|
231
|
+
|
|
232
|
+
def _init_from_cpp(self, cpp_type: cpp.StructType):
|
|
233
|
+
"""Initialize instance attributes from a C++ type object."""
|
|
234
|
+
super()._init_from_cpp(cpp_type)
|
|
235
|
+
# For wrap_cpp path, we need to convert C++ fields back to Python
|
|
236
|
+
self.fields = [(name, _get_esi_type(ty)) for (name, ty) in cpp_type.fields]
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def bit_width(self) -> int:
|
|
240
|
+
widths = [ty.bit_width for (_, ty) in self.fields]
|
|
241
|
+
if any([w < 0 for w in widths]):
|
|
242
|
+
return -1
|
|
243
|
+
return sum(widths)
|
|
244
|
+
|
|
245
|
+
def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
|
|
246
|
+
fields_count = 0
|
|
247
|
+
if not isinstance(obj, dict):
|
|
248
|
+
if not hasattr(obj, "__dict__"):
|
|
249
|
+
return (False, "must be a dict or have __dict__ attribute")
|
|
250
|
+
obj = obj.__dict__
|
|
251
|
+
|
|
252
|
+
for (fname, ftype) in self.fields:
|
|
253
|
+
if fname not in obj:
|
|
254
|
+
return (False, f"missing field '{fname}'")
|
|
255
|
+
fvalid, reason = ftype.is_valid(obj[fname])
|
|
256
|
+
if not fvalid:
|
|
257
|
+
return (False, f"invalid field '{fname}': {reason}")
|
|
258
|
+
fields_count += 1
|
|
259
|
+
if fields_count != len(obj):
|
|
260
|
+
return (False, "missing fields")
|
|
261
|
+
return (True, None)
|
|
262
|
+
|
|
263
|
+
def serialize(self, obj) -> bytearray:
|
|
264
|
+
ret = bytearray()
|
|
265
|
+
if not isinstance(obj, dict):
|
|
266
|
+
obj = obj.__dict__
|
|
267
|
+
ordered_fields = reversed(
|
|
268
|
+
self.fields) if self.cpp_type.reverse else self.fields
|
|
269
|
+
for (fname, ftype) in ordered_fields:
|
|
270
|
+
fval = obj[fname]
|
|
271
|
+
ret.extend(ftype.serialize(fval))
|
|
272
|
+
return ret
|
|
273
|
+
|
|
274
|
+
def deserialize(self, data: bytearray) -> Tuple[Dict[str, Any], bytearray]:
|
|
275
|
+
ret = {}
|
|
276
|
+
ordered_fields = reversed(
|
|
277
|
+
self.fields) if self.cpp_type.reverse else self.fields
|
|
278
|
+
for (fname, ftype) in ordered_fields:
|
|
279
|
+
(fval, data) = ftype.deserialize(data)
|
|
280
|
+
ret[fname] = fval
|
|
281
|
+
return (ret, data)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
__esi_mapping[cpp.StructType] = StructType
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class ArrayType(ESIType):
|
|
288
|
+
|
|
289
|
+
def __init__(self, id: str, element_type: "ESIType", size: int):
|
|
290
|
+
self._init_from_cpp(cpp.ArrayType(id, element_type.cpp_type, size))
|
|
291
|
+
|
|
292
|
+
def _init_from_cpp(self, cpp_type: cpp.ArrayType):
|
|
293
|
+
"""Initialize instance attributes from a C++ type object."""
|
|
294
|
+
super()._init_from_cpp(cpp_type)
|
|
295
|
+
self.element_type = _get_esi_type(cpp_type.element)
|
|
296
|
+
self.size = cpp_type.size
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def bit_width(self) -> int:
|
|
300
|
+
return self.element_type.bit_width * self.size
|
|
301
|
+
|
|
302
|
+
def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
|
|
303
|
+
if not isinstance(obj, list):
|
|
304
|
+
return (False, f"must be a list, not {type(obj)}")
|
|
305
|
+
if len(obj) != self.size:
|
|
306
|
+
return (False, f"wrong size: expected {self.size} not {len(obj)}")
|
|
307
|
+
for (idx, e) in enumerate(obj):
|
|
308
|
+
evalid, reason = self.element_type.is_valid(e)
|
|
309
|
+
if not evalid:
|
|
310
|
+
return (False, f"invalid element {idx}: {reason}")
|
|
311
|
+
return (True, None)
|
|
312
|
+
|
|
313
|
+
def serialize(self, lst: list) -> bytearray:
|
|
314
|
+
ret = bytearray()
|
|
315
|
+
for e in reversed(lst):
|
|
316
|
+
ret.extend(self.element_type.serialize(e))
|
|
317
|
+
return ret
|
|
318
|
+
|
|
319
|
+
def deserialize(self, data: bytearray) -> Tuple[List[Any], bytearray]:
|
|
320
|
+
ret = []
|
|
321
|
+
for _ in range(self.size):
|
|
322
|
+
(obj, data) = self.element_type.deserialize(data)
|
|
323
|
+
ret.append(obj)
|
|
324
|
+
ret.reverse()
|
|
325
|
+
return (ret, data)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
__esi_mapping[cpp.ArrayType] = ArrayType
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class Port:
|
|
332
|
+
"""A unidirectional communication channel. This is the basic communication
|
|
333
|
+
method with an accelerator."""
|
|
334
|
+
|
|
335
|
+
def __init__(self, owner: BundlePort, cpp_port: cpp.ChannelPort):
|
|
336
|
+
self.owner = owner
|
|
337
|
+
self.cpp_port = cpp_port
|
|
338
|
+
self.type = _get_esi_type(cpp_port.type)
|
|
339
|
+
|
|
340
|
+
def connect(self, buffer_size: Optional[int] = None):
|
|
341
|
+
(supports_host, reason) = self.type.supports_host
|
|
342
|
+
if not supports_host:
|
|
343
|
+
raise TypeError(f"unsupported type: {reason}")
|
|
344
|
+
|
|
345
|
+
opts = cpp.ConnectOptions()
|
|
346
|
+
opts.buffer_size = buffer_size
|
|
347
|
+
self.cpp_port.connect(opts)
|
|
348
|
+
return self
|
|
349
|
+
|
|
350
|
+
def disconnect(self):
|
|
351
|
+
self.cpp_port.disconnect()
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class WritePort(Port):
|
|
355
|
+
"""A unidirectional communication channel from the host to the accelerator."""
|
|
356
|
+
|
|
357
|
+
def __init__(self, owner: BundlePort, cpp_port: cpp.WriteChannelPort):
|
|
358
|
+
super().__init__(owner, cpp_port)
|
|
359
|
+
self.cpp_port: cpp.WriteChannelPort = cpp_port
|
|
360
|
+
|
|
361
|
+
def __serialize_msg(self, msg=None) -> bytearray:
|
|
362
|
+
valid, reason = self.type.is_valid(msg)
|
|
363
|
+
if not valid:
|
|
364
|
+
raise ValueError(
|
|
365
|
+
f"'{msg}' cannot be converted to '{self.type}': {reason}")
|
|
366
|
+
msg_bytes: bytearray = self.type.serialize(msg)
|
|
367
|
+
return msg_bytes
|
|
368
|
+
|
|
369
|
+
def write(self, msg=None) -> bool:
|
|
370
|
+
"""Write a typed message to the channel. Attempts to serialize 'msg' to what
|
|
371
|
+
the accelerator expects, but will fail if the object is not convertible to
|
|
372
|
+
the port type."""
|
|
373
|
+
self.cpp_port.write(self.__serialize_msg(msg))
|
|
374
|
+
return True
|
|
375
|
+
|
|
376
|
+
def try_write(self, msg=None) -> bool:
|
|
377
|
+
"""Like 'write', but uses the non-blocking tryWrite method of the underlying
|
|
378
|
+
port. Returns True if the write was successful, False otherwise."""
|
|
379
|
+
return self.cpp_port.tryWrite(self.__serialize_msg(msg))
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
class ReadPort(Port):
|
|
383
|
+
"""A unidirectional communication channel from the accelerator to the host."""
|
|
384
|
+
|
|
385
|
+
def __init__(self, owner: BundlePort, cpp_port: cpp.ReadChannelPort):
|
|
386
|
+
super().__init__(owner, cpp_port)
|
|
387
|
+
self.cpp_port: cpp.ReadChannelPort = cpp_port
|
|
388
|
+
|
|
389
|
+
def read(self) -> object:
|
|
390
|
+
"""Read a typed message from the channel. Returns a deserialized object of a
|
|
391
|
+
type defined by the port type."""
|
|
392
|
+
|
|
393
|
+
buffer = self.cpp_port.read()
|
|
394
|
+
(msg, leftover) = self.type.deserialize(buffer)
|
|
395
|
+
if len(leftover) != 0:
|
|
396
|
+
raise ValueError(f"leftover bytes: {leftover}")
|
|
397
|
+
return msg
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class BundlePort:
|
|
401
|
+
"""A collections of named, unidirectional communication channels."""
|
|
402
|
+
|
|
403
|
+
# When creating a new port, we need to determine if it is a service port and
|
|
404
|
+
# instantiate it correctly.
|
|
405
|
+
def __new__(cls, owner: HWModule, cpp_port: cpp.BundlePort):
|
|
406
|
+
# TODO: add a proper registration mechanism for service ports.
|
|
407
|
+
if isinstance(cpp_port, cpp.Function):
|
|
408
|
+
return super().__new__(FunctionPort)
|
|
409
|
+
if isinstance(cpp_port, cpp.Callback):
|
|
410
|
+
return super().__new__(CallbackPort)
|
|
411
|
+
if isinstance(cpp_port, cpp.MMIORegion):
|
|
412
|
+
return super().__new__(MMIORegion)
|
|
413
|
+
if isinstance(cpp_port, cpp.Metric):
|
|
414
|
+
return super().__new__(MetricPort)
|
|
415
|
+
return super().__new__(cls)
|
|
416
|
+
|
|
417
|
+
def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
|
|
418
|
+
self.owner = owner
|
|
419
|
+
self.cpp_port = cpp_port
|
|
420
|
+
|
|
421
|
+
def write_port(self, channel_name: str) -> WritePort:
|
|
422
|
+
return WritePort(self, self.cpp_port.getWrite(channel_name))
|
|
423
|
+
|
|
424
|
+
def read_port(self, channel_name: str) -> ReadPort:
|
|
425
|
+
return ReadPort(self, self.cpp_port.getRead(channel_name))
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
class MessageFuture(Future):
|
|
429
|
+
"""A specialization of `Future` for ESI messages. Wraps the cpp object and
|
|
430
|
+
deserializes the result. Hopefully overrides all the methods necessary for
|
|
431
|
+
proper operation, which is assumed to be not all of them."""
|
|
432
|
+
|
|
433
|
+
def __init__(self, result_type: Type, cpp_future: cpp.MessageDataFuture):
|
|
434
|
+
self.result_type = result_type
|
|
435
|
+
self.cpp_future = cpp_future
|
|
436
|
+
|
|
437
|
+
def running(self) -> bool:
|
|
438
|
+
return True
|
|
439
|
+
|
|
440
|
+
def done(self) -> bool:
|
|
441
|
+
return self.cpp_future.valid()
|
|
442
|
+
|
|
443
|
+
def result(self, timeout: Optional[Union[int, float]] = None) -> Any:
|
|
444
|
+
# TODO: respect timeout
|
|
445
|
+
self.cpp_future.wait()
|
|
446
|
+
result_bytes = self.cpp_future.get()
|
|
447
|
+
(msg, leftover) = self.result_type.deserialize(result_bytes)
|
|
448
|
+
if len(leftover) != 0:
|
|
449
|
+
raise ValueError(f"leftover bytes: {leftover}")
|
|
450
|
+
return msg
|
|
451
|
+
|
|
452
|
+
def add_done_callback(self, fn: Callable[[Future], object]) -> None:
|
|
453
|
+
raise NotImplementedError("add_done_callback is not implemented")
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class MMIORegion(BundlePort):
|
|
457
|
+
"""A region of memory-mapped I/O space. This is a collection of named
|
|
458
|
+
channels, which are either read or read-write. The channels are accessed
|
|
459
|
+
by name, and can be connected to the host."""
|
|
460
|
+
|
|
461
|
+
def __init__(self, owner: HWModule, cpp_port: cpp.MMIORegion):
|
|
462
|
+
super().__init__(owner, cpp_port)
|
|
463
|
+
self.region = cpp_port
|
|
464
|
+
|
|
465
|
+
@property
|
|
466
|
+
def descriptor(self) -> cpp.MMIORegionDesc:
|
|
467
|
+
return self.region.descriptor
|
|
468
|
+
|
|
469
|
+
def read(self, offset: int) -> bytearray:
|
|
470
|
+
"""Read a value from the MMIO region at the given offset."""
|
|
471
|
+
return self.region.read(offset)
|
|
472
|
+
|
|
473
|
+
def write(self, offset: int, data: bytearray) -> None:
|
|
474
|
+
"""Write a value to the MMIO region at the given offset."""
|
|
475
|
+
self.region.write(offset, data)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
class FunctionPort(BundlePort):
|
|
479
|
+
"""A pair of channels which carry the input and output of a function."""
|
|
480
|
+
|
|
481
|
+
def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
|
|
482
|
+
super().__init__(owner, cpp_port)
|
|
483
|
+
self.arg_type = self.write_port("arg").type
|
|
484
|
+
self.result_type = self.read_port("result").type
|
|
485
|
+
self.connected = False
|
|
486
|
+
|
|
487
|
+
def connect(self):
|
|
488
|
+
self.cpp_port.connect()
|
|
489
|
+
self.connected = True
|
|
490
|
+
|
|
491
|
+
def call(self, *args: Any, **kwargs: Any) -> Future:
|
|
492
|
+
"""Call the function with the given argument and returns a future of the
|
|
493
|
+
result."""
|
|
494
|
+
|
|
495
|
+
# Accept either positional or keyword arguments, but not both.
|
|
496
|
+
if len(args) > 0 and len(kwargs) > 0:
|
|
497
|
+
raise ValueError("cannot use both positional and keyword arguments")
|
|
498
|
+
|
|
499
|
+
# Handle arguments: for single positional arg, unwrap it from tuple
|
|
500
|
+
if len(args) == 1:
|
|
501
|
+
selected = args[0]
|
|
502
|
+
elif len(args) > 1:
|
|
503
|
+
selected = args
|
|
504
|
+
else:
|
|
505
|
+
selected = kwargs
|
|
506
|
+
|
|
507
|
+
valid, reason = self.arg_type.is_valid(selected)
|
|
508
|
+
if not valid:
|
|
509
|
+
raise ValueError(
|
|
510
|
+
f"'{selected}' cannot be converted to '{self.arg_type}': {reason}")
|
|
511
|
+
arg_bytes: bytearray = self.arg_type.serialize(selected)
|
|
512
|
+
cpp_future = self.cpp_port.call(arg_bytes)
|
|
513
|
+
return MessageFuture(self.result_type, cpp_future)
|
|
514
|
+
|
|
515
|
+
def __call__(self, *args: Any, **kwds: Any) -> Future:
|
|
516
|
+
return self.call(*args, **kwds)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
class CallbackPort(BundlePort):
|
|
520
|
+
"""Callback ports are the inverse of function ports -- instead of calls to the
|
|
521
|
+
accelerator, they get called from the accelerator. Specify the function which
|
|
522
|
+
you'd like the accelerator to call when you call `connect`."""
|
|
523
|
+
|
|
524
|
+
def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
|
|
525
|
+
super().__init__(owner, cpp_port)
|
|
526
|
+
self.arg_type = self.read_port("arg").type
|
|
527
|
+
self.result_type = self.write_port("result").type
|
|
528
|
+
self.connected = False
|
|
529
|
+
|
|
530
|
+
def connect(self, cb: Callable[[Any], Any]):
|
|
531
|
+
|
|
532
|
+
def type_convert_wrapper(cb: Callable[[Any], Any],
|
|
533
|
+
msg: bytearray) -> Optional[bytearray]:
|
|
534
|
+
try:
|
|
535
|
+
(obj, leftover) = self.arg_type.deserialize(msg)
|
|
536
|
+
if len(leftover) != 0:
|
|
537
|
+
raise ValueError(f"leftover bytes: {leftover}")
|
|
538
|
+
result = cb(obj)
|
|
539
|
+
if result is None:
|
|
540
|
+
return None
|
|
541
|
+
return self.result_type.serialize(result)
|
|
542
|
+
except Exception as e:
|
|
543
|
+
traceback.print_exception(e)
|
|
544
|
+
return None
|
|
545
|
+
|
|
546
|
+
self.cpp_port.connect(lambda x: type_convert_wrapper(cb=cb, msg=x))
|
|
547
|
+
self.connected = True
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
class MetricPort(BundlePort):
|
|
551
|
+
"""Telemetry ports report an individual piece of information from the
|
|
552
|
+
acceelerator. The method of accessing telemetry will likely change in the
|
|
553
|
+
future."""
|
|
554
|
+
|
|
555
|
+
def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
|
|
556
|
+
super().__init__(owner, cpp_port)
|
|
557
|
+
self.connected = False
|
|
558
|
+
|
|
559
|
+
def connect(self):
|
|
560
|
+
self.cpp_port.connect()
|
|
561
|
+
self.connected = True
|
|
562
|
+
|
|
563
|
+
def read(self) -> Future:
|
|
564
|
+
cpp_future = self.cpp_port.read()
|
|
565
|
+
return MessageFuture(self.cpp_port.type, cpp_future)
|
esiaccel/utils.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
2
|
+
# See https://llvm.org/LICENSE.txt for license information.
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
4
|
+
|
|
5
|
+
from . import codegen
|
|
6
|
+
|
|
7
|
+
import platform
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
_thisdir = Path(__file__).absolute().resolve().parent
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run_esiquery():
|
|
16
|
+
"""Run the esiquery executable with the same arguments as this script."""
|
|
17
|
+
if platform.system() == "Windows":
|
|
18
|
+
esiquery = _thisdir / "esiquery.exe"
|
|
19
|
+
else:
|
|
20
|
+
esiquery = _thisdir / "bin" / "esiquery"
|
|
21
|
+
return subprocess.call([esiquery] + sys.argv[1:])
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def run_esi_cosim():
|
|
25
|
+
"""Run the esi-cosim.py script with the same arguments as this script."""
|
|
26
|
+
import importlib.util
|
|
27
|
+
if platform.system() == "Windows":
|
|
28
|
+
esi_cosim = _thisdir / "esi-cosim.py"
|
|
29
|
+
else:
|
|
30
|
+
esi_cosim = _thisdir / "bin" / "esi-cosim.py"
|
|
31
|
+
spec = importlib.util.spec_from_file_location("esi_cosim", esi_cosim)
|
|
32
|
+
assert spec is not None
|
|
33
|
+
assert spec.loader is not None
|
|
34
|
+
cosim_import = importlib.util.module_from_spec(spec)
|
|
35
|
+
spec.loader.exec_module(cosim_import)
|
|
36
|
+
return cosim_import.__main__(sys.argv)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def run_cppgen():
|
|
40
|
+
return codegen.run()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_cmake_dir() -> Path:
|
|
44
|
+
return _thisdir / "cmake"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_dll_dir() -> Path:
|
|
48
|
+
"""Return the directory where the ESI dll's are located"""
|
|
49
|
+
import sys
|
|
50
|
+
import os
|
|
51
|
+
if sys.platform == "win32":
|
|
52
|
+
return _thisdir
|
|
53
|
+
else:
|
|
54
|
+
return _thisdir / "lib"
|
esiaccel/zlib1.dll
ADDED
|
Binary file
|