CIRCT 24.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 value = int.from_bytes(data[:self.max_sizemax_size], "little")
291 value &= (1 << self.bit_width) - 1
292 if self.bit_width and value & (1 << (self.bit_width - 1)):
293 value -= 1 << self.bit_width
294 return (value, data[self.max_sizemax_size:])
295
296
297__esi_mapping[cpp.SIntType] = SIntType
298
299
301
302 def __init__(self, id: str, fields: List[Tuple[str, "ESIType"]]):
303 # Convert Python ESIType fields to cpp Type fields
304 cpp_fields = [(name, field_type.cpp_type) for name, field_type in fields]
305 self._init_from_cpp_init_from_cpp(cpp.StructType(id, cpp_fields))
306
307 def _init_from_cpp(self, cpp_type: cpp.StructType):
308 """Initialize instance attributes from a C++ type object."""
309 super()._init_from_cpp(cpp_type)
310 # For wrap_cpp path, we need to convert C++ fields back to Python
311 self.fields = [(name, _get_esi_type(ty)) for (name, ty) in cpp_type.fields]
312
313 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
314 fields_count = 0
315 if not isinstance(obj, dict):
316 if not hasattr(obj, "__dict__"):
317 return (False, "must be a dict or have __dict__ attribute")
318 obj = obj.__dict__
319
320 for (fname, ftype) in self.fields:
321 if fname not in obj:
322 return (False, f"missing field '{fname}'")
323 fvalid, reason = ftype.is_valid(obj[fname])
324 if not fvalid:
325 return (False, f"invalid field '{fname}': {reason}")
326 fields_count += 1
327 if fields_count != len(obj):
328 return (False, "missing fields")
329 return (True, None)
330
331 def serialize(self, obj) -> bytearray:
332 ret = bytearray()
333 if not isinstance(obj, dict):
334 obj = obj.__dict__
335 ordered_fields = reversed(
336 self.fields) if self.cpp_type.reverse else self.fields
337 for (fname, ftype) in ordered_fields:
338 fval = obj[fname]
339 ret.extend(ftype.serialize(fval))
340 return ret
341
342 def deserialize(self, data: bytearray) -> Tuple[Dict[str, Any], bytearray]:
343 ret = {}
344 ordered_fields = reversed(
345 self.fields) if self.cpp_type.reverse else self.fields
346 for (fname, ftype) in ordered_fields:
347 (fval, data) = ftype.deserialize(data)
348 ret[fname] = fval
349 return (ret, data)
350
351
352__esi_mapping[cpp.StructType] = StructType
353
354
356
357 def __init__(self, id: str, element_type: "ESIType", size: int):
358 self._init_from_cpp_init_from_cpp(cpp.ArrayType(id, element_type.cpp_type, size))
359
360 def _init_from_cpp(self, cpp_type: cpp.ArrayType):
361 """Initialize instance attributes from a C++ type object."""
362 super()._init_from_cpp(cpp_type)
363 self.element_type = _get_esi_type(cpp_type.element)
364 self.size = cpp_type.size
365
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):
372 evalid, reason = self.element_type.is_valid(e)
373 if not evalid:
374 return (False, f"invalid element {idx}: {reason}")
375 return (True, None)
376
377 def serialize(self, lst: list) -> bytearray:
378 ret = bytearray()
379 for e in reversed(lst):
380 ret.extend(self.element_type.serialize(e))
381 return ret
382
383 def deserialize(self, data: bytearray) -> Tuple[List[Any], bytearray]:
384 ret = []
385 for _ in range(self.size):
386 (obj, data) = self.element_type.deserialize(data)
387 ret.append(obj)
388 ret.reverse()
389 return (ret, data)
390
391
392__esi_mapping[cpp.ArrayType] = ArrayType
393
394
396
397 def __init__(self, id: str, element_type: "ESIType"):
398 self._init_from_cpp_init_from_cpp(cpp.ListType(id, element_type.cpp_type))
399
400 def _init_from_cpp(self, cpp_type: cpp.ListType):
401 super()._init_from_cpp(cpp_type)
402 self.element_type = _get_esi_type(cpp_type.element)
403
404 @property
405 def supports_host(self) -> Tuple[bool, Optional[str]]:
406 return (False, "list types require an enclosing window encoding")
407
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):
412 valid, reason = self.element_type.is_valid(element)
413 if not valid:
414 return (False, f"invalid element {idx}: {reason}")
415 return (True, None)
416
417 def serialize(self, obj) -> bytearray:
418 raise ValueError("list type cannot be serialized without a window")
419
420 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
421 raise ValueError("list type cannot be deserialized without a window")
422
423
424__esi_mapping[cpp.ListType] = ListType
425
426
428
429 _HOST_UNSUPPORTED_REASON = (
430 "window types require into/lowered translation and are not yet "
431 "supported for host communication")
432
433 class Field(NamedTuple):
434 name: str
435 num_items: int
436 bulk_count_width: int
437
438 class Frame(NamedTuple):
439 name: str
440 fields: List["WindowType.Field"]
441
442 def __init__(self, id: str, name: str, into_type: "ESIType",
443 lowered_type: "ESIType", frames: List["WindowType.Frame"]):
444 cpp_frames = [
445 cpp.WindowFrame(frame.name, [
446 cpp.WindowField(field.name, field.num_items, field.bulk_count_width)
447 for field in frame.fields
448 ])
449 for frame in frames
450 ]
452 cpp.WindowType(id, name, into_type.cpp_type, lowered_type.cpp_type,
453 cpp_frames))
454
455 def _init_from_cpp(self, cpp_type: cpp.WindowType):
456 super()._init_from_cpp(cpp_type)
457 self.name = cpp_type.name
458 self.into_type = _get_esi_type(cpp_type.into)
459 self.lowered_type = _get_esi_type(cpp_type.lowered)
460 self.frames = [
461 WindowType.Frame(frame.name, [
462 WindowType.Field(field.name, field.num_items,
463 field.bulk_count_width) for field in frame.fields
464 ]) for frame in cpp_type.frames
465 ]
466
467 @property
468 def supports_host(self) -> Tuple[bool, Optional[str]]:
470
471 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
473
474 def serialize(self, obj) -> bytearray:
476
477 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
479
480
481__esi_mapping[cpp.WindowType] = WindowType
482
483
485
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]
488 self._init_from_cpp_init_from_cpp(cpp.UnionType(id, cpp_fields))
489
490 def _init_from_cpp(self, cpp_type: cpp.UnionType):
491 """Initialize instance attributes from a C++ type object."""
492 super()._init_from_cpp(cpp_type)
493 self.fields = [(name, _get_esi_type(ty)) for (name, ty) in cpp_type.fields]
494
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")
498 if len(obj) != 1:
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")
508
509 def serialize(self, obj) -> bytearray:
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
518 # Union members share the low bits; padding follows in byte order.
519 union_bytes = (self.bit_width + 7) // 8
520 pad_len = union_bytes - len(field_bytes)
521 if pad_len > 0:
522 return field_bytes + bytearray(pad_len)
523 return field_bytes
524 raise ValueError(f"unknown field '{active_name}' in union")
525
526 def deserialize(self, data: bytearray) -> Tuple[Dict[str, Any], bytearray]:
527 union_bytes = (self.bit_width + 7) // 8
528 if len(data) < union_bytes:
529 raise ValueError(
530 f"insufficient data for union: expected {union_bytes} bytes, "
531 f"got {len(data)}")
532 union_data = data[:union_bytes]
533 remaining = data[union_bytes:]
534 result = {}
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)
541 result[fname] = fval
542 return (result, remaining)
543
544
545__esi_mapping[cpp.UnionType] = UnionType
546
547
549
550 def __init__(self, id: str, name: str, inner_type: "ESIType"):
551 self._init_from_cpp_init_from_cpp(cpp.TypeAliasType(id, name, inner_type.cpp_type))
552
553 def _init_from_cpp(self, cpp_type: cpp.TypeAliasType):
554 super()._init_from_cpp(cpp_type)
555 self.name = cpp_type.name
556 self.inner_type = _get_esi_type(cpp_type.inner)
557
558 def is_valid(self, obj) -> Tuple[bool, Optional[str]]:
559 return self.inner_type.is_valid(obj)
560
561 def serialize(self, obj) -> bytearray:
562 return self.inner_type.serialize(obj)
563
564 def deserialize(self, data: bytearray) -> Tuple[object, bytearray]:
565 return self.inner_type.deserialize(data)
566
567 def __str__(self) -> str:
568 return self.name
569
570
571__esi_mapping[cpp.TypeAliasType] = TypeAlias
572
573
574class Port:
575 """A unidirectional communication channel. This is the basic communication
576 method with an accelerator."""
577
578 def __init__(self, owner: BundlePort, cpp_port: cpp.ChannelPort):
579 self.owner = owner
580 self.cpp_port = cpp_port
581 self.type = _get_esi_type(cpp_port.type)
582 win = cpp_port.windowType
583 self.window_type = _get_esi_type(win) if win is not None else None
584
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}")
589
590 opts = cpp.ConnectOptions()
591 opts.buffer_size = buffer_size
592 self.cpp_port.connect(opts)
593 return self
594
595 def disconnect(self):
596 self.cpp_port.disconnect()
597
598
600 """A unidirectional communication channel from the host to the accelerator."""
601
602 def __init__(self, owner: BundlePort, cpp_port: cpp.WriteChannelPort):
603 super().__init__(owner, cpp_port)
604 self.cpp_port: cpp.WriteChannelPort = cpp_port
605
606 def __serialize_msg(self, msg=None) -> bytearray:
607 valid, reason = self.type.is_valid(msg)
608 if not valid:
609 raise ValueError(
610 f"'{msg}' cannot be converted to '{self.type}': {reason}")
611 msg_bytes: bytearray = self.type.serialize(msg)
612 return msg_bytes
613
614 def write(self, msg=None) -> bool:
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
617 the port type."""
618 self.cpp_port.write(self.__serialize_msg(msg))
619 return True
620
621 def try_write(self, msg=None) -> bool:
622 """Like 'write', but uses the non-blocking tryWrite method of the underlying
623 port. Returns True if the write was successful, False otherwise."""
624 return self.cpp_port.tryWrite(self.__serialize_msg(msg))
625
626
628 """A unidirectional communication channel from the accelerator to the host."""
629
630 def __init__(self, owner: BundlePort, cpp_port: cpp.ReadChannelPort):
631 super().__init__(owner, cpp_port)
632 self.cpp_port: cpp.ReadChannelPort = cpp_port
633
634 def read(self) -> object:
635 """Read a typed message from the channel. Returns a deserialized object of a
636 type defined by the port type."""
637
638 buffer = self.cpp_port.read()
639 (msg, leftover) = self.type.deserialize(buffer)
640 if len(leftover) != 0:
641 raise ValueError(f"leftover bytes: {leftover}")
642 return msg
643
644
646 """A collections of named, unidirectional communication channels."""
647
648 # When creating a new port, we need to determine if it is a service port and
649 # instantiate it correctly.
650 def __new__(cls, owner: HWModule, cpp_port: cpp.BundlePort):
651 # TODO: add a proper registration mechanism for service ports.
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)
664 return super().__new__(cls)
665
666 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
667 self.owner = owner
668 self.cpp_port = cpp_port
669
670 def write_port(self, channel_name: str) -> WritePort:
671 return WritePort(self, self.cpp_port.getWrite(channel_name))
672
673 def read_port(self, channel_name: str) -> ReadPort:
674 return ReadPort(self, self.cpp_port.getRead(channel_name))
675
676
677class MessageFuture(Future):
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."""
681
682 def __init__(self, result_type: Type, cpp_future: cpp.MessageDataFuture):
683 self.result_type = result_type
684 self.cpp_future = cpp_future
685
686 def running(self) -> bool:
687 return True
688
689 def done(self) -> bool:
690 return self.cpp_future.valid()
691
692 def result(self, timeout: Optional[Union[int, float]] = None) -> Any:
693 # TODO: respect timeout
694 self.cpp_future.wait()
695 result_bytes = self.cpp_future.get()
696 (msg, leftover) = self.result_type.deserialize(result_bytes)
697 if len(leftover) != 0:
698 raise ValueError(f"leftover bytes: {leftover}")
699 return msg
700
701 def add_done_callback(self, fn: Callable[[Future], object]) -> None:
702 raise NotImplementedError("add_done_callback is not implemented")
703
704
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."""
709
710 def __init__(self, owner: HWModule, cpp_port: cpp.MMIORegion):
711 super().__init__(owner, cpp_port)
712 self.region = cpp_port
713
714 @property
715 def descriptor(self) -> cpp.MMIORegionDesc:
716 return self.region.descriptor
717
718 def read(self, offset: int) -> bytearray:
719 """Read a value from the MMIO region at the given offset."""
720 return self.region.read(offset)
721
722 def write(self, offset: int, data: bytearray) -> None:
723 """Write a value to the MMIO region at the given offset."""
724 self.region.write(offset, data)
725
726
728 """A pair of channels which carry the input and output of a function."""
729
730 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
731 super().__init__(owner, cpp_port)
732 arg_port = self.write_port("arg")
733 self.arg_type = arg_port.type
734 self.arg_window_type = arg_port.window_type
735 result_port = self.read_port("result")
736 self.result_type = result_port.type
737 self.result_window_type = result_port.window_type
738 self.connected = False
739
740 def connect(self):
741 self.cpp_port.connect()
742 self.connected = True
743
744 def call(self, *args: Any, **kwargs: Any) -> Future:
745 """Call the function with the given argument and returns a future of the
746 result."""
747
748 # Accept either positional or keyword arguments, but not both.
749 if len(args) > 0 and len(kwargs) > 0:
750 raise ValueError("cannot use both positional and keyword arguments")
751
752 # Handle arguments: for single positional arg, unwrap it from tuple
753 if len(args) == 1:
754 selected = args[0]
755 elif len(args) > 1:
756 selected = args
757 else:
758 selected = kwargs
759
760 valid, reason = self.arg_type.is_valid(selected)
761 if not valid:
762 raise ValueError(
763 f"'{selected}' cannot be converted to '{self.arg_type}': {reason}")
764 arg_bytes: bytearray = self.arg_type.serialize(selected)
765 cpp_future = self.cpp_port.call(arg_bytes)
766 return MessageFuture(self.result_type, cpp_future)
767
768 def __call__(self, *args: Any, **kwds: Any) -> Future:
769 return self.call(*args, **kwds)
770
771
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`."""
776
777 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
778 super().__init__(owner, cpp_port)
779 arg_port = self.read_port("arg")
780 self.arg_type = arg_port.type
781 self.arg_window_type = arg_port.window_type
782 result_port = self.write_port("result")
783 self.result_type = result_port.type
784 self.result_window_type = result_port.window_type
785 self.connected = False
786
787 def connect(self, cb: Callable[[Any], Any]):
788
789 def type_convert_wrapper(cb: Callable[[Any], Any],
790 msg: bytearray) -> Optional[bytearray]:
791 try:
792 (obj, leftover) = self.arg_type.deserialize(msg)
793 if len(leftover) != 0:
794 raise ValueError(f"leftover bytes: {leftover}")
795 result = cb(obj)
796 if result is None:
797 return None
798 return self.result_type.serialize(result)
799 except Exception as e:
800 traceback.print_exception(e)
801 return None
802
803 self.cpp_port.connect(lambda x: type_convert_wrapper(cb=cb, msg=x))
804 self.connected = True
805
806
808 """Telemetry ports report an individual piece of information from the
809 acceelerator. The method of accessing telemetry will likely change in the
810 future."""
811
812 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
813 super().__init__(owner, cpp_port)
814 self.connected = False
815
816 def connect(self):
817 self.cpp_port.connect()
818 self.connected = True
819
820 def read(self) -> Future:
821 cpp_future = self.cpp_port.read()
822 return MessageFuture(self.cpp_port.type, cpp_future)
823
824
826 """A channel which reads data from the accelerator (to_host)."""
827
828 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
829 super().__init__(owner, cpp_port)
830 data_port = self.read_port("data")
831 self.data_type = data_port.type
832 self.data_window_type = data_port.window_type
833 self.connected = False
834
835 def connect(self):
836 self.cpp_port.connect()
837 self.connected = True
838
839 def read(self) -> Future:
840 """Read a value from the channel. Returns a future."""
841 cpp_future = self.cpp_port.read()
842 return MessageFuture(self.data_type, cpp_future)
843
844
846 """A channel which writes data to the accelerator (from_host)."""
847
848 def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort):
849 super().__init__(owner, cpp_port)
850 data_port = self.write_port("data")
851 self.data_type = data_port.type
852 self.data_window_type = data_port.window_type
853 self.connected = False
854
855 def connect(self):
856 self.cpp_port.connect()
857 self.connected = True
858
859 def write(self, data: Any) -> None:
860 """Write a value to the channel."""
861 valid, reason = self.data_type.is_valid(data)
862 if not valid:
863 raise ValueError(
864 f"'{data}' cannot be converted to '{self.data_type}': {reason}")
865 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:366
_init_from_cpp(self, cpp.ArrayType cpp_type)
Definition types.py:360
Tuple[List[Any], bytearray] deserialize(self, bytearray data)
Definition types.py:383
__init__(self, str id, "ESIType" element_type, int size)
Definition types.py:357
bytearray serialize(self, list lst)
Definition types.py:377
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:650
WritePort write_port(self, str channel_name)
Definition types.py:670
ReadPort read_port(self, str channel_name)
Definition types.py:673
__init__(self, HWModule owner, cpp.BundlePort cpp_port)
Definition types.py:666
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:777
connect(self, Callable[[Any], Any] cb)
Definition types.py:787
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:848
None write(self, Any data)
Definition types.py:859
Future call(self, *Any args, **Any kwargs)
Definition types.py:744
__init__(self, HWModule owner, cpp.BundlePort cpp_port)
Definition types.py:730
Future __call__(self, *Any args, **Any kwds)
Definition types.py:768
__init__(self, str id, int width)
Definition types.py:237
bytearray serialize(self, obj)
Definition types.py:417
__init__(self, str id, "ESIType" element_type)
Definition types.py:397
_init_from_cpp(self, cpp.ListType cpp_type)
Definition types.py:400
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:408
Tuple[object, bytearray] deserialize(self, bytearray data)
Definition types.py:420
Tuple[bool, Optional[str]] supports_host(self)
Definition types.py:405
None write(self, int offset, bytearray data)
Definition types.py:722
bytearray read(self, int offset)
Definition types.py:718
__init__(self, HWModule owner, cpp.MMIORegion cpp_port)
Definition types.py:710
cpp.MMIORegionDesc descriptor(self)
Definition types.py:715
Any result(self, Optional[Union[int, float]] timeout=None)
Definition types.py:692
__init__(self, Type result_type, cpp.MessageDataFuture cpp_future)
Definition types.py:682
None add_done_callback(self, Callable[[Future], object] fn)
Definition types.py:701
Future read(self)
Definition types.py:820
__init__(self, HWModule owner, cpp.BundlePort cpp_port)
Definition types.py:812
__init__(self, BundlePort owner, cpp.ChannelPort cpp_port)
Definition types.py:578
connect(self, Optional[int] buffer_size=None)
Definition types.py:585
__init__(self, BundlePort owner, cpp.ReadChannelPort cpp_port)
Definition types.py:630
object read(self)
Definition types.py:634
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:302
bytearray serialize(self, obj)
Definition types.py:331
Tuple[Dict[str, Any], bytearray] deserialize(self, bytearray data)
Definition types.py:342
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:313
_init_from_cpp(self, cpp.StructType cpp_type)
Definition types.py:307
Future read(self)
Definition types.py:839
__init__(self, HWModule owner, cpp.BundlePort cpp_port)
Definition types.py:828
Tuple[object, bytearray] deserialize(self, bytearray data)
Definition types.py:564
_init_from_cpp(self, cpp.TypeAliasType cpp_type)
Definition types.py:553
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:558
bytearray serialize(self, obj)
Definition types.py:561
__init__(self, str id, str name, "ESIType" inner_type)
Definition types.py:550
__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:490
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:495
bytearray serialize(self, obj)
Definition types.py:509
Tuple[Dict[str, Any], bytearray] deserialize(self, bytearray data)
Definition types.py:526
__init__(self, str id, List[Tuple[str, "ESIType"]] fields)
Definition types.py:486
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:477
__init__(self, str id, str name, "ESIType" into_type, "ESIType" lowered_type, List["WindowType.Frame"] frames)
Definition types.py:443
_init_from_cpp(self, cpp.WindowType cpp_type)
Definition types.py:455
Tuple[bool, Optional[str]] supports_host(self)
Definition types.py:468
Tuple[bool, Optional[str]] is_valid(self, obj)
Definition types.py:471
bytearray serialize(self, obj)
Definition types.py:474
bool try_write(self, msg=None)
Definition types.py:621
bytearray __serialize_msg(self, msg=None)
Definition types.py:606
bool write(self, msg=None)
Definition types.py:614
__init__(self, BundlePort owner, cpp.WriteChannelPort cpp_port)
Definition types.py:602
_get_esi_type(cpp.Type cpp_type)
Definition types.py:28