13from __future__
import annotations
15from .
import esiCppAccel
as cpp
17from typing
import TYPE_CHECKING
20 from .accelerator
import HWModule
22from concurrent.futures
import Future
23from typing
import Any, Callable, Dict, List, NamedTuple, Optional, Tuple, Type, Union
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)
37__esi_mapping: Dict[Type, 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)
53 """Initialize instance attributes from a C++ type object."""
58 """Get the stable id of this type."""
63 """Does this type support host communication via Python? Returns either
64 '(True, None)' if it is, or '(False, reason)' if it is not."""
67 return (
False,
"runtime only supports types with multiple of 8 bits")
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"
77 """Size of this type, in bits. Negative for unbounded types."""
82 """Maximum size of a value of this type, in bytes."""
89 """Convert a Python object to a bytearray."""
90 assert False,
"unimplemented"
92 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
93 """Convert a bytearray to a Python object. Return the object and the
95 assert False,
"unimplemented"
101 return isinstance(other, ESIType)
and self.
idid == other.id
124 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
130 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
134__esi_mapping[cpp.ChannelType] = ChannelType
141 direction: cpp.BundleType.Direction
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]
153 for name, direction, channel_type
in cpp_type.channels
161__esi_mapping[cpp.BundleType] = BundleType
169 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
171 return (
False, f
"void type must be represented by None, not {obj}")
179 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
185__esi_mapping[cpp.VoidType] = VoidType
193 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
194 return (
False,
"any type is not supported for host communication")
197 raise ValueError(
"any type cannot be serialized")
199 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
200 raise ValueError(
"any type cannot be deserialized")
203__esi_mapping[cpp.AnyType] = AnyType
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}")
218 return (
False, f
"wrong size: {len(obj)}")
221 def serialize(self, obj: Union[bytearray, bytes, List[int]]) -> bytearray:
222 if isinstance(obj, bytearray):
224 if isinstance(obj, bytes)
or isinstance(obj, list):
225 return bytearray(obj)
226 raise ValueError(f
"cannot convert {obj} to bytearray")
228 def deserialize(self, data: bytearray) -> Tuple[bytearray, bytearray]:
232__esi_mapping[cpp.BitsType] = BitsType
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}")
254 return f
"uint{self.bit_width}"
264__esi_mapping[cpp.UIntType] = UIntType
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)}")
277 return (
False, f
"out of range: {obj}")
280 return (
False, f
"out of range: {obj}")
284 return f
"sint{self.bit_width}"
297__esi_mapping[cpp.SIntType] = SIntType
302 def __init__(self, id: str, fields: List[Tuple[str,
"ESIType"]]):
304 cpp_fields = [(name, field_type.cpp_type)
for name, field_type
in fields]
308 """Initialize instance attributes from a C++ type object."""
313 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
315 if not isinstance(obj, dict):
316 if not hasattr(obj,
"__dict__"):
317 return (
False,
"must be a dict or have __dict__ attribute")
320 for (fname, ftype)
in self.
fields:
322 return (
False, f
"missing field '{fname}'")
323 fvalid, reason = ftype.is_valid(obj[fname])
325 return (
False, f
"invalid field '{fname}': {reason}")
327 if fields_count != len(obj):
328 return (
False,
"missing fields")
333 if not isinstance(obj, dict):
335 ordered_fields = reversed(
337 for (fname, ftype)
in ordered_fields:
339 ret.extend(ftype.serialize(fval))
342 def deserialize(self, data: bytearray) -> Tuple[Dict[str, Any], bytearray]:
344 ordered_fields = reversed(
346 for (fname, ftype)
in ordered_fields:
347 (fval, data) = ftype.deserialize(data)
352__esi_mapping[cpp.StructType] = StructType
357 def __init__(self, id: str, element_type:
"ESIType", size: int):
361 """Initialize instance attributes from a C++ type object."""
366 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
367 if not isinstance(obj, list):
368 return (
False, f
"must be a list, not {type(obj)}")
369 if len(obj) != self.
size:
370 return (
False, f
"wrong size: expected {self.size} not {len(obj)}")
371 for (idx, e)
in enumerate(obj):
374 return (
False, f
"invalid element {idx}: {reason}")
379 for e
in reversed(lst):
383 def deserialize(self, data: bytearray) -> Tuple[List[Any], bytearray]:
385 for _
in range(self.
size):
392__esi_mapping[cpp.ArrayType] = ArrayType
397 def __init__(self, id: str, element_type:
"ESIType"):
406 return (
False,
"list types require an enclosing window encoding")
408 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
409 if not isinstance(obj, list):
410 return (
False, f
"must be a list, not {type(obj)}")
411 for (idx, element)
in enumerate(obj):
414 return (
False, f
"invalid element {idx}: {reason}")
418 raise ValueError(
"list type cannot be serialized without a window")
420 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
421 raise ValueError(
"list type cannot be deserialized without a window")
424__esi_mapping[cpp.ListType] = ListType
429 _HOST_UNSUPPORTED_REASON = (
430 "window types require into/lowered translation and are not yet "
431 "supported for host communication")
436 bulk_count_width: int
440 fields: List[
"WindowType.Field"]
442 def __init__(self, id: str, name: str, into_type:
"ESIType",
443 lowered_type:
"ESIType", frames: List[
"WindowType.Frame"]):
445 cpp.WindowFrame(frame.name, [
446 cpp.WindowField(field.name, field.num_items, field.bulk_count_width)
447 for field
in frame.fields
452 cpp.WindowType(id, name, into_type.cpp_type, lowered_type.cpp_type,
463 field.bulk_count_width)
for field
in frame.fields
464 ])
for frame
in cpp_type.frames
471 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
477 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
481__esi_mapping[cpp.WindowType] = WindowType
486 def __init__(self, id: str, fields: List[Tuple[str,
"ESIType"]]):
487 cpp_fields = [(name, field_type.cpp_type)
for name, field_type
in fields]
491 """Initialize instance attributes from a C++ type object."""
495 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
496 if not isinstance(obj, dict):
497 return (
False,
"must be a dict with exactly one field")
499 return (
False, f
"union must have exactly 1 active field, got {len(obj)}")
500 field_names = {name
for name, _
in self.
fields}
501 active_name = next(iter(obj))
502 if active_name
not in field_names:
503 return (
False, f
"unknown field '{active_name}' in union")
504 for (fname, ftype)
in self.
fields:
505 if fname == active_name:
506 return ftype.is_valid(obj[active_name])
507 return (
False, f
"unknown field '{active_name}' in union")
510 if not isinstance(obj, dict)
or len(obj) != 1:
511 raise ValueError(
"union value must be a dict with exactly one field")
512 active_name = next(iter(obj))
513 for (fname, ftype)
in self.
fields:
514 if fname == active_name:
515 field_bytes = bytearray(ftype.serialize(obj[active_name]))
516 if ftype.bit_width % 8:
517 field_bytes[-1] &= (1 << (ftype.bit_width % 8)) - 1
520 pad_len = union_bytes - len(field_bytes)
522 return field_bytes + bytearray(pad_len)
524 raise ValueError(f
"unknown field '{active_name}' in union")
526 def deserialize(self, data: bytearray) -> Tuple[Dict[str, Any], bytearray]:
528 if len(data) < union_bytes:
530 f
"insufficient data for union: expected {union_bytes} bytes, "
532 union_data = data[:union_bytes]
533 remaining = data[union_bytes:]
535 for (fname, ftype)
in self.
fields:
536 field_bytes = (ftype.bit_width + 7) // 8
537 field_data = bytearray(union_data[:field_bytes])
538 if ftype.bit_width % 8:
539 field_data[-1] &= (1 << (ftype.bit_width % 8)) - 1
540 (fval, _) = ftype.deserialize(field_data)
542 return (result, remaining)
545__esi_mapping[cpp.UnionType] = UnionType
550 def __init__(self, id: str, name: str, inner_type:
"ESIType"):
558 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
564 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
571__esi_mapping[cpp.TypeAliasType] = TypeAlias
575 """A unidirectional communication channel. This is the basic communication
576 method with an accelerator."""
578 def __init__(self, owner: BundlePort, cpp_port: cpp.ChannelPort):
582 win = cpp_port.windowType
585 def connect(self, buffer_size: Optional[int] =
None):
586 (supports_host, reason) = self.
type.supports_host
587 if not supports_host:
588 raise TypeError(f
"unsupported type: {reason}")
590 opts = cpp.ConnectOptions()
591 opts.buffer_size = buffer_size
600 """A unidirectional communication channel from the host to the accelerator."""
602 def __init__(self, owner: BundlePort, cpp_port: cpp.WriteChannelPort):
604 self.
cpp_port: cpp.WriteChannelPort = cpp_port
607 valid, reason = self.
type.is_valid(msg)
610 f
"'{msg}' cannot be converted to '{self.type}': {reason}")
611 msg_bytes: bytearray = self.
type.serialize(msg)
615 """Write a typed message to the channel. Attempts to serialize 'msg' to what
616 the accelerator expects, but will fail if the object is not convertible to
622 """Like 'write', but uses the non-blocking tryWrite method of the underlying
623 port. Returns True if the write was successful, False otherwise."""
628 """A unidirectional communication channel from the accelerator to the host."""
630 def __init__(self, owner: BundlePort, cpp_port: cpp.ReadChannelPort):
632 self.
cpp_port: cpp.ReadChannelPort = cpp_port
635 """Read a typed message from the channel. Returns a deserialized object of a
636 type defined by the port type."""
639 (msg, leftover) = self.
type.deserialize(buffer)
640 if len(leftover) != 0:
641 raise ValueError(f
"leftover bytes: {leftover}")
646 """A collections of named, unidirectional communication channels."""
650 def __new__(cls, owner: HWModule, cpp_port: cpp.BundlePort):
652 if isinstance(cpp_port, cpp.Function):
653 return super().
__new__(FunctionPort)
654 if isinstance(cpp_port, cpp.Callback):
655 return super().
__new__(CallbackPort)
656 if isinstance(cpp_port, cpp.MMIORegion):
657 return super().
__new__(MMIORegion)
658 if isinstance(cpp_port, cpp.Metric):
659 return super().
__new__(MetricPort)
660 if isinstance(cpp_port, cpp.ToHostChannel):
661 return super().
__new__(ToHostPort)
662 if isinstance(cpp_port, cpp.FromHostChannel):
663 return super().
__new__(FromHostPort)
666 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
678 """A specialization of `Future` for ESI messages. Wraps the cpp object and
679 deserializes the result. Hopefully overrides all the methods necessary for
680 proper operation, which is assumed to be not all of them."""
682 def __init__(self, result_type: Type, cpp_future: cpp.MessageDataFuture):
692 def result(self, timeout: Optional[Union[int, float]] =
None) -> Any:
696 (msg, leftover) = self.
result_type.deserialize(result_bytes)
697 if len(leftover) != 0:
698 raise ValueError(f
"leftover bytes: {leftover}")
702 raise NotImplementedError(
"add_done_callback is not implemented")
706 """A region of memory-mapped I/O space. This is a collection of named
707 channels, which are either read or read-write. The channels are accessed
708 by name, and can be connected to the host."""
710 def __init__(self, owner: HWModule, cpp_port: cpp.MMIORegion):
716 return self.
region.descriptor
718 def read(self, offset: int) -> bytearray:
719 """Read a value from the MMIO region at the given offset."""
722 def write(self, offset: int, data: bytearray) ->
None:
723 """Write a value to the MMIO region at the given offset."""
728 """A pair of channels which carry the input and output of a function."""
730 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
744 def call(self, *args: Any, **kwargs: Any) -> Future:
745 """Call the function with the given argument and returns a future of the
749 if len(args) > 0
and len(kwargs) > 0:
750 raise ValueError(
"cannot use both positional and keyword arguments")
760 valid, reason = self.
arg_type.is_valid(selected)
763 f
"'{selected}' cannot be converted to '{self.arg_type}': {reason}")
764 arg_bytes: bytearray = self.
arg_type.serialize(selected)
768 def __call__(self, *args: Any, **kwds: Any) -> Future:
769 return self.
call(*args, **kwds)
773 """Callback ports are the inverse of function ports -- instead of calls to the
774 accelerator, they get called from the accelerator. Specify the function which
775 you'd like the accelerator to call when you call `connect`."""
777 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
789 def type_convert_wrapper(cb: Callable[[Any], Any],
790 msg: bytearray) -> Optional[bytearray]:
792 (obj, leftover) = self.
arg_type.deserialize(msg)
793 if len(leftover) != 0:
794 raise ValueError(f
"leftover bytes: {leftover}")
799 except Exception
as e:
800 traceback.print_exception(e)
808 """Telemetry ports report an individual piece of information from the
809 acceelerator. The method of accessing telemetry will likely change in the
812 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
826 """A channel which reads data from the accelerator (to_host)."""
828 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
840 """Read a value from the channel. Returns a future."""
846 """A channel which writes data to the accelerator (from_host)."""
848 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
860 """Write a value to the channel."""
861 valid, reason = self.
data_type.is_valid(data)
864 f
"'{data}' cannot be converted to '{self.data_type}': {reason}")
Tuple[object, bytearray] deserialize(self, bytearray data)
bytearray serialize(self, obj)
Tuple[bool, Optional[str]] is_valid(self, obj)
Tuple[bool, Optional[str]] is_valid(self, obj)
_init_from_cpp(self, cpp.ArrayType cpp_type)
Tuple[List[Any], bytearray] deserialize(self, bytearray data)
__init__(self, str id, "ESIType" element_type, int size)
bytearray serialize(self, list lst)
bytearray serialize(self, Union[bytearray, bytes, List[int]] obj)
Tuple[bytearray, bytearray] deserialize(self, bytearray data)
Tuple[bool, Optional[str]] is_valid(self, obj)
__init__(self, str id, int width)
__new__(cls, HWModule owner, cpp.BundlePort cpp_port)
WritePort write_port(self, str channel_name)
ReadPort read_port(self, str channel_name)
__init__(self, HWModule owner, cpp.BundlePort cpp_port)
List["BundleType.Channel"] channels(self)
_init_from_cpp(self, cpp.BundleType cpp_type)
__init__(self, str id, List[Channel] channels)
__init__(self, HWModule owner, cpp.BundlePort cpp_port)
connect(self, Callable[[Any], Any] cb)
bytearray serialize(self, obj)
_init_from_cpp(self, cpp.ChannelType cpp_type)
Tuple[bool, Optional[str]] is_valid(self, obj)
Tuple[object, bytearray] deserialize(self, bytearray data)
__init__(self, str id, "ESIType" inner)
Tuple[bool, Optional[str]] supports_host(self)
Tuple[bool, Optional[str]] supports_host(self)
Tuple[object, bytearray] deserialize(self, bytearray data)
_init_from_cpp(self, cpp.Type cpp_type)
Tuple[bool, Optional[str]] is_valid(self, obj)
wrap_cpp(cls, cpp.Type cpp_type)
bytearray serialize(self, obj)
__init__(self, HWModule owner, cpp.BundlePort cpp_port)
None write(self, Any data)
Future call(self, *Any args, **Any kwargs)
__init__(self, HWModule owner, cpp.BundlePort cpp_port)
Future __call__(self, *Any args, **Any kwds)
__init__(self, str id, int width)
bytearray serialize(self, obj)
__init__(self, str id, "ESIType" element_type)
_init_from_cpp(self, cpp.ListType cpp_type)
Tuple[bool, Optional[str]] is_valid(self, obj)
Tuple[object, bytearray] deserialize(self, bytearray data)
Tuple[bool, Optional[str]] supports_host(self)
None write(self, int offset, bytearray data)
bytearray read(self, int offset)
__init__(self, HWModule owner, cpp.MMIORegion cpp_port)
cpp.MMIORegionDesc descriptor(self)
Any result(self, Optional[Union[int, float]] timeout=None)
__init__(self, Type result_type, cpp.MessageDataFuture cpp_future)
None add_done_callback(self, Callable[[Future], object] fn)
__init__(self, HWModule owner, cpp.BundlePort cpp_port)
__init__(self, BundlePort owner, cpp.ChannelPort cpp_port)
connect(self, Optional[int] buffer_size=None)
__init__(self, BundlePort owner, cpp.ReadChannelPort cpp_port)
Tuple[int, bytearray] deserialize(self, bytearray data)
Tuple[bool, Optional[str]] is_valid(self, obj)
bytearray serialize(self, int obj)
__init__(self, str id, int width)
__init__(self, str id, List[Tuple[str, "ESIType"]] fields)
bytearray serialize(self, obj)
Tuple[Dict[str, Any], bytearray] deserialize(self, bytearray data)
Tuple[bool, Optional[str]] is_valid(self, obj)
_init_from_cpp(self, cpp.StructType cpp_type)
__init__(self, HWModule owner, cpp.BundlePort cpp_port)
Tuple[object, bytearray] deserialize(self, bytearray data)
_init_from_cpp(self, cpp.TypeAliasType cpp_type)
Tuple[bool, Optional[str]] is_valid(self, obj)
bytearray serialize(self, obj)
__init__(self, str id, str name, "ESIType" inner_type)
__init__(self, str id, int width)
Tuple[int, bytearray] deserialize(self, bytearray data)
Tuple[bool, Optional[str]] is_valid(self, obj)
bytearray serialize(self, int obj)
_init_from_cpp(self, cpp.UnionType cpp_type)
Tuple[bool, Optional[str]] is_valid(self, obj)
bytearray serialize(self, obj)
Tuple[Dict[str, Any], bytearray] deserialize(self, bytearray data)
__init__(self, str id, List[Tuple[str, "ESIType"]] fields)
Tuple[object, bytearray] deserialize(self, bytearray data)
Tuple[bool, Optional[str]] is_valid(self, obj)
bytearray serialize(self, obj)
Tuple[object, bytearray] deserialize(self, bytearray data)
tuple _HOST_UNSUPPORTED_REASON
__init__(self, str id, str name, "ESIType" into_type, "ESIType" lowered_type, List["WindowType.Frame"] frames)
_init_from_cpp(self, cpp.WindowType cpp_type)
Tuple[bool, Optional[str]] supports_host(self)
Tuple[bool, Optional[str]] is_valid(self, obj)
bytearray serialize(self, obj)
bool try_write(self, msg=None)
bytearray __serialize_msg(self, msg=None)
bool write(self, msg=None)
__init__(self, BundlePort owner, cpp.WriteChannelPort cpp_port)
_get_esi_type(cpp.Type cpp_type)