Newer
Older
Angus Lothian
committed
Contains classes for managing the ports of operations.
Angus Lothian
committed
from copy import copy
from numbers import Number
from typing import TYPE_CHECKING, List, Optional, Sequence, Union
Angus Lothian
committed
from b_asic.graph_component import Name
from b_asic.types import Num
Angus Lothian
committed
if TYPE_CHECKING:
from b_asic.core_operations import (
Addition,
ConstantMultiplication,
Division,
Multiplication,
Reciprocal,
RightShift,
Shift,
Angus Lothian
committed
from b_asic.operation import Operation
Angus Lothian
committed
Ports serve as connection points for connecting signals between operations.
They also store information about the latency of the corresponding
calculations of the operation.
Aside from connected signals, each port also provides a reference to the
parent operation that "owns" it as well as the operation's port index at
which the port resides.
Angus Lothian
committed
def operation(self) -> "Operation":
"""Return the connected operation."""
raise NotImplementedError
@property
@abstractmethod
Angus Lothian
committed
def index(self) -> int:
"""Return the index of the port."""
raise NotImplementedError
@property
@abstractmethod
Angus Lothian
committed
"""Get the latency_offset of the port."""
Angus Lothian
committed
@latency_offset.setter
Angus Lothian
committed
def latency_offset(self, latency_offset: int) -> None:
"""Set the latency_offset of the port to the integer specified value."""
raise NotImplementedError
@property
@abstractmethod
def signal_count(self) -> int:
"""Return the number of connected signals."""
raise NotImplementedError
Angus Lothian
committed
@property
Angus Lothian
committed
"""Return all connected signals."""
raise NotImplementedError
@abstractmethod
def add_signal(self, signal: Signal) -> None:
Connect this port to the entered signal.
If the entered signal is not connected to this port then connect the entered
signal to the port as well.
Parameters
----------
signal : Signal
Signal to add.
Angus Lothian
committed
"""
raise NotImplementedError
@abstractmethod
def remove_signal(self, signal: Signal) -> None:
"""
Remove the signal that was entered from the Ports signals.
If the entered signal still is connected to this port then disconnect the
Angus Lothian
committed
entered signal from the port as well.
"""
raise NotImplementedError
@abstractmethod
def clear(self) -> None:
"""Remove all connected signals from the Port."""
@property
@abstractmethod
def name(self) -> str:
Return a name of the port.
This name consists of *graph_id* of the related operation and the port number.
"""
Generic abstract port base class.
Angus Lothian
committed
Concrete ports should normally derive from this to get the default
behavior.
__slots__ = ("_operation", "_index", "_latency_offset")
Angus Lothian
committed
_operation: "Operation"
Angus Lothian
committed
_latency_offset: Optional[int]
def __init__(
self,
operation: "Operation",
index: int,
latency_offset: Optional[int] = None,
):
Angus Lothian
committed
"""Construct a port of the given operation at the given port index."""
Angus Lothian
committed
self._index = index
self._latency_offset = latency_offset
Angus Lothian
committed
def operation(self) -> "Operation":
Angus Lothian
committed
def index(self) -> int:
Angus Lothian
committed
@property
Angus Lothian
committed
return self._latency_offset
@latency_offset.setter
def latency_offset(self, latency_offset: Optional[int]):
Angus Lothian
committed
self._latency_offset = latency_offset
@property
def name(self):
return f"{self.operation.graph_id}.{self.index}"
Angus Lothian
committed
class SignalSourceProvider(ABC):
"""
Signal source provider interface.
Angus Lothian
committed
Signal source providers give access to a single output port that can be
used to connect signals from.
"""
@property
@abstractmethod
def source(self) -> "OutputPort":
"""Get the main source port provided by this object."""
raise NotImplementedError
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
def __add__(self, src: Union["SignalSourceProvider", Num]) -> "Addition":
# Import here to avoid circular imports.
from b_asic.core_operations import Addition, Constant
if isinstance(src, Number):
return Addition(self, Constant(src))
else:
return Addition(self, src)
def __radd__(self, src: Union["SignalSourceProvider", Num]) -> "Addition":
# Import here to avoid circular imports.
from b_asic.core_operations import Addition, Constant
return Addition(Constant(src) if isinstance(src, Number) else src, self)
def __sub__(self, src: Union["SignalSourceProvider", Num]) -> "Subtraction":
# Import here to avoid circular imports.
from b_asic.core_operations import Constant, Subtraction
return Subtraction(self, Constant(src) if isinstance(src, Number) else src)
def __rsub__(self, src: Union["SignalSourceProvider", Num]) -> "Subtraction":
# Import here to avoid circular imports.
from b_asic.core_operations import Constant, Subtraction
return Subtraction(Constant(src) if isinstance(src, Number) else src, self)
def __mul__(
self, src: Union["SignalSourceProvider", Num]
) -> Union["Multiplication", "ConstantMultiplication"]:
# Import here to avoid circular imports.
from b_asic.core_operations import ConstantMultiplication, Multiplication
return (
ConstantMultiplication(src, self)
if isinstance(src, Number)
else Multiplication(self, src)
)
def __rmul__(
self, src: Union["SignalSourceProvider", Num]
) -> Union["Multiplication", "ConstantMultiplication"]:
# Import here to avoid circular imports.
from b_asic.core_operations import ConstantMultiplication, Multiplication
return (
ConstantMultiplication(src, self)
if isinstance(src, Number)
else Multiplication(src, self)
)
def __truediv__(self, src: Union["SignalSourceProvider", Num]) -> "Division":
# Import here to avoid circular imports.
from b_asic.core_operations import Constant, Division
return Division(self, Constant(src) if isinstance(src, Number) else src)
def __rtruediv__(
self, src: Union["SignalSourceProvider", Num]
) -> Union["Division", "Reciprocal"]:
# Import here to avoid circular imports.
from b_asic.core_operations import Constant, Division, Reciprocal
if isinstance(src, Number):
if src == 1:
return Reciprocal(self)
else:
return Division(Constant(src), self)
return Division(src, self)
def __lshift__(self, src: int) -> Union["LeftShift", "Shift"]:
from b_asic.core_operations import LeftShift, Shift
if not isinstance(src, int):
raise TypeError("Can only shift with an int")
if src >= 0:
return LeftShift(src, self)
return Shift(src, self)
def __rshift__(self, src: int) -> Union["RightShift", "Shift"]:
from b_asic.core_operations import RightShift, Shift
if not isinstance(src, int):
raise TypeError("Can only shift with an int")
if src >= 0:
return RightShift(src, self)
return Shift(-src, self)
Angus Lothian
committed
May have one or zero signals connected to it.
Angus Lothian
committed
def __init__(self, operation: "Operation", index: int):
"""Construct an InputPort."""
super().__init__(operation, index)
self._source_signal = None
@property
def signal_count(self) -> int:
return 0 if self._source_signal is None else 1
Angus Lothian
committed
@property
Angus Lothian
committed
return [] if self._source_signal is None else [self._source_signal]
def add_signal(self, signal: Signal) -> None:
if self._source_signal is not None:
raise ValueError("Cannot add to already connected input port.")
if signal is self._source_signal:
raise ValueError("Cannot add same signal twice")
Angus Lothian
committed
self._source_signal = signal
signal.set_destination(self)
def remove_signal(self, signal: Signal) -> None:
if signal is not self._source_signal:
raise ValueError("Cannot remove signal that is not connected")
Angus Lothian
committed
signal.remove_destination()
Angus Lothian
committed
if self._source_signal is not None:
self.remove_signal(self._source_signal)
@property
def connected_source(self) -> Optional["OutputPort"]:
Get the output port that is currently connected to this input port.
Return None if it is unconnected.
Angus Lothian
committed
"""
return None if self._source_signal is None else self._source_signal.source
Angus Lothian
committed
def connect(self, src: SignalSourceProvider, name: Name = Name("")) -> Signal:
"""
Connect the provided signal source to this input port by creating a new signal.
Angus Lothian
committed
Returns the new signal.
"""
if self._source_signal is not None:
raise ValueError("Cannot connect already connected input port.")
if isinstance(src, Signal):
src.set_destination(self)
return src
Angus Lothian
committed
# self._source_signal is set by the signal constructor.
return Signal(source=src.source, destination=self, name=Name(name))
Angus Lothian
committed
def __ilshift__(self, src: SignalSourceProvider) -> "InputPort":
Connect the provided signal source to this input port.
Returns the new signal.
Angus Lothian
committed
"""
self.connect(src)
return self
Angus Lothian
committed
def delay(self, number: int) -> "InputPort":
"""
Insert *number* amount of delay elements before the input port.
Returns the input port of the first delay element in the chain.
"""
from b_asic.special_operations import Delay
if not isinstance(number, int) or number < 0:
raise TypeError("Number of delays must be a positive integer")
tmp_signal = None
if any(self.signals):
tmp_signal = self.signals[0]
tmp_signal.remove_destination()
current = self
for i in range(number):
d = Delay()
current.connect(d)
current = d.input(0)
if tmp_signal is not None:
tmp_signal.set_destination(current)
return current
Angus Lothian
committed
class OutputPort(AbstractPort, SignalSourceProvider):
Angus Lothian
committed
May have zero or more signals connected to it.
Angus Lothian
committed
def __init__(self, operation: "Operation", index: int):
"""Construct an OutputPort."""
super().__init__(operation, index)
self._destination_signals = []
@property
def signal_count(self) -> int:
return len(self._destination_signals)
Angus Lothian
committed
@property
Angus Lothian
committed
return self._destination_signals
def add_signal(self, signal: Signal) -> None:
if signal in self._destination_signals:
raise ValueError("Cannot add same signal twice")
Angus Lothian
committed
signal.set_source(self)
def remove_signal(self, signal: Signal) -> None:
if signal not in self._destination_signals:
raise ValueError("Cannot remove signal that is not connected")
Angus Lothian
committed
self._destination_signals.remove(signal)
signal.remove_source()
Angus Lothian
committed
for signal in copy(self._destination_signals):
Angus Lothian
committed
@property
def source(self) -> "OutputPort":
return self