CIRCT 23.0.0git
Loading...
Searching...
No Matches
types.py
Go to the documentation of this file.
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
13from __future__ import annotations
14
15from . import esiCppAccel as cpp
16
17from typing import TYPE_CHECKING
18
19if TYPE_CHECKING:
20 from .accelerator import HWModule
21
22from concurrent.futures import Future
23from typing import Any, Callable, Dict, List, NamedTuple, Optional, Tuple, Type, Union
24import sys
25import traceback
26
27
28def _get_esi_type(cpp_type: cpp.Type):
29 """Get the wrapper class for a C++ type."""
30 for cpp_type_cls, wrapper_cls in __esi_mapping.items():
31 if isinstance(cpp_type, cpp_type_cls):
32 return wrapper_cls.wrap_cpp(cpp_type)
33 return ESIType.wrap_cpp(cpp_type)
34
35
36# Mapping from C++ types to wrapper classes
37__esi_mapping: Dict[Type, Type] = {}
38
39
40class ESIType:
41
42 def __init__(self, id: str):
43 self._init_from_cpp(cpp.Type(id))
44
45 @classmethod
46 def wrap_cpp(cls, cpp_type: cpp.Type):
47 """Wrap a C++ ESI type with its corresponding Python ESI Type."""
48 instance = cls.__new__(cls)
49 instance._init_from_cpp(cpp_type)
50 return instance
51
52 def _init_from_cpp(self, cpp_type: cpp.Type):
53 """Initialize instance attributes from a C++ type object."""
54 self.cpp_type = cpp_type
55
56 @property
57 def id(self) -> str:
58 """Get the stable id of this type."""
59 return self.cpp_type.id
60
61 @property
62 def supports_host(self) -> Tuple[bool, Optional[str]]:
63 """Does this type support host communication via Python? Returns either
64 '(True, None)' if it is, or '(False, reason)' if it is not."""
65
66 if self.bit_width >= 0 and self.bit_width % 8 != 0:
67 return (False, "runtime only supports types with multiple of 8 bits")
68 return (True, None)
69
70 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
71 """Is a Python object compatible with HW type? Returns either '(True,
72 None)' if it is, or '(False, reason)' if it is not."""
73 assert False, "unimplemented"
74
75 @property
76 def bit_width(self) -> int:
77 """Size of this type, in bits. Negative for unbounded types."""
78 return self.cpp_type.bit_width
79
80 @property
81 def max_size(self) -> int:
82 """Maximum size of a value of this type, in bytes."""
83 bitwidth = int((self.bit_width + 7) / 8)
84 if bitwidth < 0:
85 return bitwidth
86 return bitwidth
87
88 def serialize(self, obj) -> bytearray:
89 """Convert a Python object to a bytearray."""
90 assert False, "unimplemented"
91
92 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
93 """Convert a bytearray to a Python object. Return the object and the
94 leftover bytes."""
95 assert False, "unimplemented"
96
97 def __hash__(self) -> int:
98 return hash(self.idid)
99
100 def __eq__(self, other) -> bool:
101 return isinstance(other, ESIType) and self.idid == other.id
102
103 def __str__(self) -> str:
104 return str(self.cpp_type)
105
106
108
109 def __init__(self, id: str, inner: "ESIType"):
110 self._init_from_cpp_init_from_cpp(cpp.ChannelType(id, inner.cpp_type))
111
112 def _init_from_cpp(self, cpp_type: cpp.ChannelType):
113 super()._init_from_cpp(cpp_type)
114 self.inner_type = _get_esi_type(cpp_type.inner)
115
116 @property
117 def inner(self) -> "ESIType":
118 return self.inner_type
119
120 @property
121 def supports_host(self) -> Tuple[bool, Optional[str]]:
122 return self.inner_type.supports_host
123
124 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
125 return self.inner_type.is_valid(obj)
126
127 def serialize(self, obj) -> bytearray:
128 return self.inner_type.serialize(obj)
129
130 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
131 return self.inner_type.deserialize(data)
132
133
134__esi_mapping[cpp.ChannelType] = ChannelType
135
136
138
139 class Channel(NamedTuple):
140 name: str
141 direction: cpp.BundleType.Direction
142 type: "ESIType"
143
144 def __init__(self, id: str, channels: List[Channel]):
145 cpp_channels = [(name, direction, channel_type.cpp_type)
146 for name, direction, channel_type in channels]
147 self._init_from_cpp_init_from_cpp(cpp.BundleType(id, cpp_channels))
148
149 def _init_from_cpp(self, cpp_type: cpp.BundleType):
150 super()._init_from_cpp(cpp_type)
151 self._channels = [
152 BundleType.Channel(name, direction, _get_esi_type(channel_type))
153 for name, direction, channel_type in cpp_type.channels
154 ]
155
156 @property
157 def channels(self) -> List["BundleType.Channel"]:
158 return self._channels
159
160
161__esi_mapping[cpp.BundleType] = BundleType
162
163
165
166 def __init__(self, id: str):
167 self._init_from_cpp(cpp.VoidType(id))
168
169 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
170 if obj is not None:
171 return (False, f"void type must be represented by None, not {obj}")
172 return (True, None)
173
174 def serialize(self, obj) -> bytearray:
175 # Void carries no logical data. Transports that require a non-empty
176 # payload are responsible for adding their own placeholder byte.
177 return bytearray()
178
179 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
180 # Transports that pad empty messages to one byte strip that byte before
181 # invoking deserialize, so we consume nothing here.
182 return (None, data)
183
184
185__esi_mapping[cpp.VoidType] = VoidType
186
187
189
190 def __init__(self, id: str):
191 self._init_from_cpp(cpp.AnyType(id))
192
193 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
194 return (False, "any type is not supported for host communication")
195
196 def serialize(self, obj) -> bytearray:
197 raise ValueError("any type cannot be serialized")
198
199 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
200 raise ValueError("any type cannot be deserialized")
201
202
203__esi_mapping[cpp.AnyType] = AnyType
204
205
207
208 def __init__(self, id: str, width: int):
209 self._init_from_cpp(cpp.BitsType(id, width))
210
211 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
212 if not isinstance(obj, (bytearray, bytes, list)):
213 return (False, f"invalid type: {type(obj)}")
214 if isinstance(obj, list) and not all(
215 [isinstance(b, int) and b.bit_length() <= 8 for b in obj]):
216 return (False, f"list item too large: {obj}")
217 if len(obj) != self.max_size:
218 return (False, f"wrong size: {len(obj)}")
219 return (True, None)
220
221 def serialize(self, obj: Union[bytearray, bytes, List[int]]) -> bytearray:
222 if isinstance(obj, bytearray):
223 return obj
224 if isinstance(obj, bytes) or isinstance(obj, list):
225 return bytearray(obj)
226 raise ValueError(f"cannot convert {obj} to bytearray")
227
228 def deserialize(self, data: bytearray) -> Tuple[bytearray, bytearray]:
229 return (data[0:self.max_size], data[self.max_size:])
230
231
232__esi_mapping[cpp.BitsType] = BitsType
233
234
236
237 def __init__(self, id: str, width: int):
238 self._init_from_cpp(cpp.IntegerType(id, width))
239
240
242
243 def __init__(self, id: str, width: int):
244 self._init_from_cpp(cpp.UIntType(id, width))
245
246 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
247 if not isinstance(obj, int):
248 return (False, f"must be an int, not {type(obj)}")
249 if obj < 0 or obj.bit_length() > self.bit_width:
250 return (False, f"out of range: {obj}")
251 return (True, None)
252
253 def __str__(self) -> str:
254 return f"uint{self.bit_width}"
255
256 def serialize(self, obj: int) -> bytearray:
257 return bytearray(int.to_bytes(obj, self.max_sizemax_size, "little"))
258
259 def deserialize(self, data: bytearray) -> Tuple[int, bytearray]:
260 return (int.from_bytes(data[0:self.max_sizemax_size],
261 "little"), data[self.max_sizemax_size:])
262
263
264__esi_mapping[cpp.UIntType] = UIntType
265
266
268
269 def __init__(self, id: str, width: int):
270 self._init_from_cpp(cpp.SIntType(id, width))
271
272 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
273 if not isinstance(obj, int):
274 return (False, f"must be an int, not {type(obj)}")
275 if obj < 0:
276 if (-1 * obj) > 2**(self.bit_width - 1):
277 return (False, f"out of range: {obj}")
278 elif obj < 0:
279 if obj >= 2**(self.bit_width - 1) - 1:
280 return (False, f"out of range: {obj}")
281 return (True, None)
282
283 def __str__(self) -> str:
284 return f"sint{self.bit_width}"
285
286 def serialize(self, obj: int) -> bytearray:
287 return bytearray(int.to_bytes(obj, self.max_sizemax_size, "little", signed=True))
288
289 def deserialize(self, data: bytearray) -> Tuple[int, bytearray]:
290 return (int.from_bytes(data[0:self.max_sizemax_size], "little",
291 signed=True), data[self.max_sizemax_size:])
292
293
294__esi_mapping[cpp.SIntType] = SIntType
295
296
298
299 def __init__(self, id: str, fields: List[Tuple[str, "ESIType"]]):
300 # Convert Python ESIType fields to cpp Type fields
301 cpp_fields = [(name, field_type.cpp_type) for name, field_type in fields]
302 self._init_from_cpp_init_from_cpp(cpp.StructType(id, cpp_fields))
303
304 def _init_from_cpp(self, cpp_type: cpp.StructType):
305 """Initialize instance attributes from a C++ type object."""
306 super()._init_from_cpp(cpp_type)
307 # For wrap_cpp path, we need to convert C++ fields back to Python
308 self.fields = [(name, _get_esi_type(ty)) for (name, ty) in cpp_type.fields]
309
310 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
311 fields_count = 0
312 if not isinstance(obj, dict):
313 if not hasattr(obj, "__dict__"):
314 return (False, "must be a dict or have __dict__ attribute")
315 obj = obj.__dict__
316
317 for (fname, ftype) in self.fields:
318 if fname not in obj:
319 return (False, f"missing field '{fname}'")
320 fvalid, reason = ftype.is_valid(obj[fname])
321 if not fvalid:
322 return (False, f"invalid field '{fname}': {reason}")
323 fields_count += 1
324 if fields_count != len(obj):
325 return (False, "missing fields")
326 return (True, None)
327
328 def serialize(self, obj) -> bytearray:
329 ret = bytearray()
330 if not isinstance(obj, dict):
331 obj = obj.__dict__
332 ordered_fields = reversed(
333 self.fields) if self.cpp_type.reverse else self.fields
334 for (fname, ftype) in ordered_fields:
335 fval = obj[fname]
336 ret.extend(ftype.serialize(fval))
337 return ret
338
339 def deserialize(self, data: bytearray) -> Tuple[Dict[str, Any], bytearray]:
340 ret = {}
341 ordered_fields = reversed(
342 self.fields) if self.cpp_type.reverse else self.fields
343 for (fname, ftype) in ordered_fields:
344 (fval, data) = ftype.deserialize(data)
345 ret[fname] = fval
346 return (ret, data)
347
348
349__esi_mapping[cpp.StructType] = StructType
350
351
353
354 def __init__(self, id: str, element_type: "ESIType", size: int):
355 self._init_from_cpp_init_from_cpp(cpp.ArrayType(id, element_type.cpp_type, size))
356
357 def _init_from_cpp(self, cpp_type: cpp.ArrayType):
358 """Initialize instance attributes from a C++ type object."""
359 super()._init_from_cpp(cpp_type)
360 self.element_type = _get_esi_type(cpp_type.element)
361 self.size = cpp_type.size
362
363 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
364 if not isinstance(obj, list):
365 return (False, f"must be a list, not {type(obj)}")
366 if len(obj) != self.size:
367 return (False, f"wrong size: expected {self.size} not {len(obj)}")
368 for (idx, e) in enumerate(obj):
369 evalid, reason = self.element_type.is_valid(e)
370 if not evalid:
371 return (False, f"invalid element {idx}: {reason}")
372 return (True, None)
373
374 def serialize(self, lst: list) -> bytearray:
375 ret = bytearray()
376 for e in reversed(lst):
377 ret.extend(self.element_type.serialize(e))
378 return ret
379
380 def deserialize(self, data: bytearray) -> Tuple[List[Any], bytearray]:
381 ret = []
382 for _ in range(self.size):
383 (obj, data) = self.element_type.deserialize(data)
384 ret.append(obj)
385 ret.reverse()
386 return (ret, data)
387
388
389__esi_mapping[cpp.ArrayType] = ArrayType
390
391
393
394 def __init__(self, id: str, element_type: "ESIType"):
395 self._init_from_cpp_init_from_cpp(cpp.ListType(id, element_type.cpp_type))
396
397 def _init_from_cpp(self, cpp_type: cpp.ListType):
398 super()._init_from_cpp(cpp_type)
399 self.element_type = _get_esi_type(cpp_type.element)
400
401 @property
402 def supports_host(self) -> Tuple[bool, Optional[str]]:
403 return (False, "list types require an enclosing window encoding")
404
405 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
406 if not isinstance(obj, list):
407 return (False, f"must be a list, not {type(obj)}")
408 for (idx, element) in enumerate(obj):
409 valid, reason = self.element_type.is_valid(element)
410 if not valid:
411 return (False, f"invalid element {idx}: {reason}")
412 return (True, None)
413
414 def serialize(self, obj) -> bytearray:
415 raise ValueError("list type cannot be serialized without a window")
416
417 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
418 raise ValueError("list type cannot be deserialized without a window")
419
420
421__esi_mapping[cpp.ListType] = ListType
422
423
425
426 _HOST_UNSUPPORTED_REASON = (
427 "window types require into/lowered translation and are not yet "
428 "supported for host communication")
429
430 class Field(NamedTuple):
431 name: str
432 num_items: int
433 bulk_count_width: int
434
435 class Frame(NamedTuple):
436 name: str
437 fields: List["WindowType.Field"]
438
439 def __init__(self, id: str, name: str, into_type: "ESIType",
440 lowered_type: "ESIType", frames: List["WindowType.Frame"]):
441 cpp_frames = [
442 cpp.WindowFrame(frame.name, [
443 cpp.WindowField(field.name, field.num_items, field.bulk_count_width)
444 for field in frame.fields
445 ])
446 for frame in frames
447 ]
449 cpp.WindowType(id, name, into_type.cpp_type, lowered_type.cpp_type,
450 cpp_frames))
451
452 def _init_from_cpp(self, cpp_type: cpp.WindowType):
453 super()._init_from_cpp(cpp_type)
454 self.name = cpp_type.name
455 self.into_type = _get_esi_type(cpp_type.into)
456 self.lowered_type = _get_esi_type(cpp_type.lowered)
457 self.frames = [
458 WindowType.Frame(frame.name, [
459 WindowType.Field(field.name, field.num_items,
460 field.bulk_count_width) for field in frame.fields
461 ]) for frame in cpp_type.frames
462 ]
463
464 @property
465 def supports_host(self) -> Tuple[bool, Optional[str]]:
467
468 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
470
471 def serialize(self, obj) -> bytearray:
473
474 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
476
477
478__esi_mapping[cpp.WindowType] = WindowType
479
480
482
483 def __init__(self, id: str, fields: List[Tuple[str, "ESIType"]]):
484 cpp_fields = [(name, field_type.cpp_type) for name, field_type in fields]
485 self._init_from_cpp_init_from_cpp(cpp.UnionType(id, cpp_fields))
486
487 def _init_from_cpp(self, cpp_type: cpp.UnionType):
488 """Initialize instance attributes from a C++ type object."""
489 super()._init_from_cpp(cpp_type)
490 self.fields = [(name, _get_esi_type(ty)) for (name, ty) in cpp_type.fields]
491
492 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
493 if not isinstance(obj, dict):
494 return (False, "must be a dict with exactly one field")
495 if len(obj) != 1:
496 return (False, f"union must have exactly 1 active field, got {len(obj)}")
497 field_names = {name for name, _ in self.fields}
498 active_name = next(iter(obj))
499 if active_name not in field_names:
500 return (False, f"unknown field '{active_name}' in union")
501 for (fname, ftype) in self.fields:
502 if fname == active_name:
503 return ftype.is_valid(obj[active_name])
504 return (False, f"unknown field '{active_name}' in union")
505
506 def serialize(self, obj) -> bytearray:
507 if not isinstance(obj, dict) or len(obj) != 1:
508 raise ValueError("union value must be a dict with exactly one field")
509 active_name = next(iter(obj))
510 for (fname, ftype) in self.fields:
511 if fname == active_name:
512 field_bytes = ftype.serialize(obj[active_name])
513 # In a packed union, padding is at LSB (beginning of byte stream)
514 # and field data is at MSB (end of byte stream).
515 union_bytes = (self.bit_width + 7) // 8
516 pad_len = union_bytes - len(field_bytes)
517 if pad_len > 0:
518 return bytearray(pad_len) + field_bytes
519 return field_bytes
520 raise ValueError(f"unknown field '{active_name}' in union")
521
522 def deserialize(self, data: bytearray) -> Tuple[Dict[str, Any], bytearray]:
523 union_bytes = (self.bit_width + 7) // 8
524 union_data = data[:union_bytes]
525 remaining = data[union_bytes:]
526 result = {}
527 for (fname, ftype) in self.fields:
528 # In a packed union, field data is at MSB (end of byte stream).
529 # Skip the LSB padding to reach each field's data.
530 field_bytes = (ftype.bit_width + 7) // 8
531 pad_len = union_bytes - field_bytes
532 (fval, _) = ftype.deserialize(bytearray(union_data[pad_len:]))
533 result[fname] = fval
534 return (result, remaining)
535
536
537__esi_mapping[cpp.UnionType] = UnionType
538
539
541
542 def __init__(self, id: str, name: str, inner_type: "ESIType"):
543 self._init_from_cpp_init_from_cpp(cpp.TypeAliasType(id, name, inner_type.cpp_type))
544
545 def _init_from_cpp(self, cpp_type: cpp.TypeAliasType):
546 super()._init_from_cpp(cpp_type)
547 self.name = cpp_type.name
548 self.inner_type = _get_esi_type(cpp_type.inner)
549
550 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
551 return self.inner_type.is_valid(obj)
552
553 def serialize(self, obj) -> bytearray:
554 return self.inner_type.serialize(obj)
555
556 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
557 return self.inner_type.deserialize(data)
558
559 def __str__(self) -> str:
560 return self.name
561
562
563__esi_mapping[cpp.TypeAliasType] = TypeAlias
564
565
566class Port:
567 """A unidirectional communication channel. This is the basic communication
568 method with an accelerator."""
569
570 def __init__(self, owner: BundlePort, cpp_port: cpp.ChannelPort):
571 self.owner = owner
572 self.cpp_port = cpp_port
573 self.type = _get_esi_type(cpp_port.type)
574 win = cpp_port.windowType
575 self.window_type = _get_esi_type(win) if win is not None else None
576
577 def connect(self, buffer_size: Optional[int] = None):
578 (supports_host, reason) = self.type.supports_host
579 if not supports_host:
580 raise TypeError(f"unsupported type: {reason}")
581
582 opts = cpp.ConnectOptions()
583 opts.buffer_size = buffer_size
584 self.cpp_port.connect(opts)
585 return self
586
587 def disconnect(self):
588 self.cpp_port.disconnect()
589
590
592 """A unidirectional communication channel from the host to the accelerator."""
593
594 def __init__(self, owner: BundlePort, cpp_port: cpp.WriteChannelPort):
595 super().__init__(owner, cpp_port)
596 self.cpp_port: cpp.WriteChannelPort = cpp_port
597
598 def __serialize_msg(self, msg=None) -> bytearray:
599 valid, reason = self.type.is_valid(msg)
600 if not valid:
601 raise ValueError(
602 f"'{msg}' cannot be converted to '{self.type}': {reason}")
603 msg_bytes: bytearray = self.type.serialize(msg)
604 return msg_bytes
605
606 def write(self, msg=None) -> bool:
607 """Write a typed message to the channel. Attempts to serialize 'msg' to what
608 the accelerator expects, but will fail if the object is not convertible to
609 the port type."""
610 self.cpp_port.write(self.__serialize_msg(msg))
611 return True
612
613 def try_write(self, msg=None) -> bool:
614 """Like 'write', but uses the non-blocking tryWrite method of the underlying
615 port. Returns True if the write was successful, False otherwise."""
616 return self.cpp_port.tryWrite(self.__serialize_msg(msg))
617
618
620 """A unidirectional communication channel from the accelerator to the host."""
621
622 def __init__(self, owner: BundlePort, cpp_port: cpp.ReadChannelPort):
623 super().__init__(owner, cpp_port)
624 self.cpp_port: cpp.ReadChannelPort = cpp_port
625
626 def read(self) -> object:
627 """Read a typed message from the channel. Returns a deserialized object of a
628 type defined by the port type."""
629
630 buffer = self.cpp_port.read()
631 (msg, leftover) = self.type.deserialize(buffer)
632 if len(leftover) != 0:
633 raise ValueError(f"leftover bytes: {leftover}")
634 return msg
635
636
638 """A collections of named, unidirectional communication channels."""
639
640 # When creating a new port, we need to determine if it is a service port and
641 # instantiate it correctly.
642 def __new__(cls, owner: HWModule, cpp_port: cpp.BundlePort):
643 # TODO: add a proper registration mechanism for service ports.
644 if isinstance(cpp_port, cpp.Function):
645 return super().__new__(FunctionPort)
646 if isinstance(cpp_port, cpp.Callback):
647 return super().__new__(CallbackPort)
648 if isinstance(cpp_port, cpp.MMIORegion):
649 return super().__new__(MMIORegion)
650 if isinstance(cpp_port, cpp.Metric):
651 return super().__new__(MetricPort)
652 if isinstance(cpp_port, cpp.ToHostChannel):
653 return super().__new__(ToHostPort)
654 if isinstance(cpp_port, cpp.FromHostChannel):
655 return super().__new__(FromHostPort)
656 return super().__new__(cls)
657
658 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
659 self.owner = owner
660 self.cpp_port = cpp_port
661
662 def write_port(self, channel_name: str) -> WritePort:
663 return WritePort(self, self.cpp_port.getWrite(channel_name))
664
665 def read_port(self, channel_name: str) -> ReadPort:
666 return ReadPort(self, self.cpp_port.getRead(channel_name))
667
668
669class MessageFuture(Future):
670 """A specialization of `Future` for ESI messages. Wraps the cpp object and
671 deserializes the result. Hopefully overrides all the methods necessary for
672 proper operation, which is assumed to be not all of them."""
673
674 def __init__(self, result_type: Type, cpp_future: cpp.MessageDataFuture):
675 self.result_type = result_type
676 self.cpp_future = cpp_future
677
678 def running(self) -> bool:
679 return True
680
681 def done(self) -> bool:
682 return self.cpp_future.valid()
683
684 def result(self, timeout: Optional[Union[int, float]] = None) -> Any:
685 # TODO: respect timeout
686 self.cpp_future.wait()
687 result_bytes = self.cpp_future.get()
688 (msg, leftover) = self.result_type.deserialize(result_bytes)
689 if len(leftover) != 0:
690 raise ValueError(f"leftover bytes: {leftover}")
691 return msg
692
693 def add_done_callback(self, fn: Callable[[Future], object]) -> None:
694 raise NotImplementedError("add_done_callback is not implemented")
695
696
698 """A region of memory-mapped I/O space. This is a collection of named
699 channels, which are either read or read-write. The channels are accessed
700 by name, and can be connected to the host."""
701
702 def __init__(self, owner: HWModule, cpp_port: cpp.MMIORegion):
703 super().__init__(owner, cpp_port)
704 self.region = cpp_port
705
706 @property
707 def descriptor(self) -> cpp.MMIORegionDesc:
708 return self.region.descriptor
709
710 def read(self, offset: int) -> bytearray:
711 """Read a value from the MMIO region at the given offset."""
712 return self.region.read(offset)
713
714 def write(self, offset: int, data: bytearray) -> None:
715 """Write a value to the MMIO region at the given offset."""
716 self.region.write(offset, data)
717
718
720 """A pair of channels which carry the input and output of a function."""
721
722 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
723 super().__init__(owner, cpp_port)
724 arg_port = self.write_port("arg")
725 self.arg_type = arg_port.type
726 self.arg_window_type = arg_port.window_type
727 result_port = self.read_port("result")
728 self.result_type = result_port.type
729 self.result_window_type = result_port.window_type
730 self.connected = False
731
732 def connect(self):
733 self.cpp_port.connect()
734 self.connected = True
735
736 def call(self, *args: Any, **kwargs: Any) -> Future:
737 """Call the function with the given argument and returns a future of the
738 result."""
739
740 # Accept either positional or keyword arguments, but not both.
741 if len(args) > 0 and len(kwargs) > 0:
742 raise ValueError("cannot use both positional and keyword arguments")
743
744 # Handle arguments: for single positional arg, unwrap it from tuple
745 if len(args) == 1:
746 selected = args[0]
747 elif len(args) > 1:
748 selected = args
749 else:
750 selected = kwargs
751
752 valid, reason = self.arg_type.is_valid(selected)
753 if not valid:
754 raise ValueError(
755 f"'{selected}' cannot be converted to '{self.arg_type}': {reason}")
756 arg_bytes: bytearray = self.arg_type.serialize(selected)
757 cpp_future = self.cpp_port.call(arg_bytes)
758 return MessageFuture(self.result_type, cpp_future)
759
760 def __call__(self, *args: Any, **kwds: Any) -> Future:
761 return self.call(*args, **kwds)
762
763
765 """Callback ports are the inverse of function ports -- instead of calls to the
766 accelerator, they get called from the accelerator. Specify the function which
767 you'd like the accelerator to call when you call `connect`."""
768
769 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
770 super().__init__(owner, cpp_port)
771 arg_port = self.read_port("arg")
772 self.arg_type = arg_port.type
773 self.arg_window_type = arg_port.window_type
774 result_port = self.write_port("result")
775 self.result_type = result_port.type
776 self.result_window_type = result_port.window_type
777 self.connected = False
778
779 def connect(self, cb: Callable[[Any], Any]):
780
781 def type_convert_wrapper(cb: Callable[[Any], Any],
782 msg: bytearray) -> Optional[bytearray]:
783 try:
784 (obj, leftover) = self.arg_type.deserialize(msg)
785 if len(leftover) != 0:
786 raise ValueError(f"leftover bytes: {leftover}")
787 result = cb(obj)
788 if result is None:
789 return None
790 return self.result_type.serialize(result)
791 except Exception as e:
792 traceback.print_exception(e)
793 return None
794
795 self.cpp_port.connect(lambda x: type_convert_wrapper(cb=cb, msg=x))
796 self.connected = True
797
798
800 """Telemetry ports report an individual piece of information from the
801 acceelerator. The method of accessing telemetry will likely change in the
802 future."""
803
804 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
805 super().__init__(owner, cpp_port)
806 self.connected = False
807
808 def connect(self):
809 self.cpp_port.connect()
810 self.connected = True
811
812 def read(self) -> Future:
813 cpp_future = self.cpp_port.read()
814 return MessageFuture(self.cpp_port.type, cpp_future)
815
816
818 """A channel which reads data from the accelerator (to_host)."""
819
820 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
821 super().__init__(owner, cpp_port)
822 data_port = self.read_port("data")
823 self.data_type = data_port.type
824 self.data_window_type = data_port.window_type
825 self.connected = False
826
827 def connect(self):
828 self.cpp_port.connect()
829 self.connected = True
830
831 def read(self) -> Future:
832 """Read a value from the channel. Returns a future."""
833 cpp_future = self.cpp_port.read()
834 return MessageFuture(self.data_type, cpp_future)
835
836
838 """A channel which writes data to the accelerator (from_host)."""
839
840 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
841 super().__init__(owner, cpp_port)
842 data_port = self.write_port("data")
843 self.data_type = data_port.type
844 self.data_window_type = data_port.window_type
845 self.connected = False
846
847 def connect(self):
848 self.cpp_port.connect()
849 self.connected = True
850
851 def write(self, data: Any) -> None:
852 """Write a value to the channel."""
853 valid, reason = self.data_type.is_valid(data)
854 if not valid:
855 raise ValueError(
856 f"'{data}' cannot be converted to '{self.data_type}': {reason}")
857 self.cpp_port.write(self.data_type.serialize(data))
__init__(self, str id)
Definition types.py:190
Tuple[object, bytearray] deserialize(self, bytearray data)
Definition types.py:199
bytearray serialize(self, obj)
Definition types.py:196
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:193
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:363
_init_from_cpp(self, cpp.ArrayType cpp_type)
Definition types.py:357
Tuple[List[Any], bytearray] deserialize(self, bytearray data)
Definition types.py:380
__init__(self, str id, "ESIType" element_type, int size)
Definition types.py:354
bytearray serialize(self, list lst)
Definition types.py:374
bytearray serialize(self, Union[bytearray, bytes, List[int]] obj)
Definition types.py:221
Tuple[bytearray, bytearray] deserialize(self, bytearray data)
Definition types.py:228
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:211
__init__(self, str id, int width)
Definition types.py:208
__new__(cls, HWModule owner, cpp.BundlePort cpp_port)
Definition types.py:642
WritePort write_port(self, str channel_name)
Definition types.py:662
ReadPort read_port(self, str channel_name)
Definition types.py:665
__init__(self, HWModule owner, cpp.BundlePort cpp_port)
Definition types.py:658
List["BundleType.Channel"] channels(self)
Definition types.py:157
_init_from_cpp(self, cpp.BundleType cpp_type)
Definition types.py:149
__init__(self, str id, List[Channel] channels)
Definition types.py:144
__init__(self, HWModule owner, cpp.BundlePort cpp_port)
Definition types.py:769
connect(self, Callable[[Any], Any] cb)
Definition types.py:779
bytearray serialize(self, obj)
Definition types.py:127
_init_from_cpp(self, cpp.ChannelType cpp_type)
Definition types.py:112
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:124
Tuple[object, bytearray] deserialize(self, bytearray data)
Definition types.py:130
__init__(self, str id, "ESIType" inner)
Definition types.py:109
Tuple[bool, Optional[str]] supports_host(self)
Definition types.py:121
"ESIType" inner(self)
Definition types.py:117
int bit_width(self)
Definition types.py:76
Tuple[bool, Optional[str]] supports_host(self)
Definition types.py:62
int __hash__(self)
Definition types.py:97
Tuple[object, bytearray] deserialize(self, bytearray data)
Definition types.py:92
_init_from_cpp(self, cpp.Type cpp_type)
Definition types.py:52
__init__(self, str id)
Definition types.py:42
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:70
bool __eq__(self, other)
Definition types.py:100
int max_size(self)
Definition types.py:81
wrap_cpp(cls, cpp.Type cpp_type)
Definition types.py:46
str __str__(self)
Definition types.py:103
bytearray serialize(self, obj)
Definition types.py:88
__init__(self, HWModule owner, cpp.BundlePort cpp_port)
Definition types.py:840
None write(self, Any data)
Definition types.py:851
Future call(self, *Any args, **Any kwargs)
Definition types.py:736
__init__(self, HWModule owner, cpp.BundlePort cpp_port)
Definition types.py:722
Future __call__(self, *Any args, **Any kwds)
Definition types.py:760
__init__(self, str id, int width)
Definition types.py:237
bytearray serialize(self, obj)
Definition types.py:414
__init__(self, str id, "ESIType" element_type)
Definition types.py:394
_init_from_cpp(self, cpp.ListType cpp_type)
Definition types.py:397
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:405
Tuple[object, bytearray] deserialize(self, bytearray data)
Definition types.py:417
Tuple[bool, Optional[str]] supports_host(self)
Definition types.py:402
None write(self, int offset, bytearray data)
Definition types.py:714
bytearray read(self, int offset)
Definition types.py:710
__init__(self, HWModule owner, cpp.MMIORegion cpp_port)
Definition types.py:702
cpp.MMIORegionDesc descriptor(self)
Definition types.py:707
Any result(self, Optional[Union[int, float]] timeout=None)
Definition types.py:684
__init__(self, Type result_type, cpp.MessageDataFuture cpp_future)
Definition types.py:674
None add_done_callback(self, Callable[[Future], object] fn)
Definition types.py:693
Future read(self)
Definition types.py:812
__init__(self, HWModule owner, cpp.BundlePort cpp_port)
Definition types.py:804
__init__(self, BundlePort owner, cpp.ChannelPort cpp_port)
Definition types.py:570
connect(self, Optional[int] buffer_size=None)
Definition types.py:577
__init__(self, BundlePort owner, cpp.ReadChannelPort cpp_port)
Definition types.py:622
object read(self)
Definition types.py:626
Tuple[int, bytearray] deserialize(self, bytearray data)
Definition types.py:289
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:272
bytearray serialize(self, int obj)
Definition types.py:286
__init__(self, str id, int width)
Definition types.py:269
__init__(self, str id, List[Tuple[str, "ESIType"]] fields)
Definition types.py:299
bytearray serialize(self, obj)
Definition types.py:328
Tuple[Dict[str, Any], bytearray] deserialize(self, bytearray data)
Definition types.py:339
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:310
_init_from_cpp(self, cpp.StructType cpp_type)
Definition types.py:304
Future read(self)
Definition types.py:831
__init__(self, HWModule owner, cpp.BundlePort cpp_port)
Definition types.py:820
Tuple[object, bytearray] deserialize(self, bytearray data)
Definition types.py:556
_init_from_cpp(self, cpp.TypeAliasType cpp_type)
Definition types.py:545
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:550
bytearray serialize(self, obj)
Definition types.py:553
__init__(self, str id, str name, "ESIType" inner_type)
Definition types.py:542
__init__(self, str id, int width)
Definition types.py:243
Tuple[int, bytearray] deserialize(self, bytearray data)
Definition types.py:259
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:246
bytearray serialize(self, int obj)
Definition types.py:256
_init_from_cpp(self, cpp.UnionType cpp_type)
Definition types.py:487
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:492
bytearray serialize(self, obj)
Definition types.py:506
Tuple[Dict[str, Any], bytearray] deserialize(self, bytearray data)
Definition types.py:522
__init__(self, str id, List[Tuple[str, "ESIType"]] fields)
Definition types.py:483
Tuple[object, bytearray] deserialize(self, bytearray data)
Definition types.py:179
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:169
__init__(self, str id)
Definition types.py:166
bytearray serialize(self, obj)
Definition types.py:174
Tuple[object, bytearray] deserialize(self, bytearray data)
Definition types.py:474
__init__(self, str id, str name, "ESIType" into_type, "ESIType" lowered_type, List["WindowType.Frame"] frames)
Definition types.py:440
_init_from_cpp(self, cpp.WindowType cpp_type)
Definition types.py:452
Tuple[bool, Optional[str]] supports_host(self)
Definition types.py:465
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:468
bytearray serialize(self, obj)
Definition types.py:471
bool try_write(self, msg=None)
Definition types.py:613
bytearray __serialize_msg(self, msg=None)
Definition types.py:598
bool write(self, msg=None)
Definition types.py:606
__init__(self, BundlePort owner, cpp.WriteChannelPort cpp_port)
Definition types.py:594
_get_esi_type(cpp.Type cpp_type)
Definition types.py:28