diff --git a/b_asic/operation.py b/b_asic/operation.py index 78f70ca2ddc39ede2c4e2fc0d794b95565212445..e39e9d6910e3d211988b7cc0913a012b8c81af83 100644 --- a/b_asic/operation.py +++ b/b_asic/operation.py @@ -407,7 +407,9 @@ class Operation(GraphComponent, SignalSourceProvider): @abstractmethod def get_plot_coordinates( self, - ) -> Tuple[List[List[float]], List[List[float]]]: + ) -> Tuple[ + Tuple[Tuple[float, float], ...], Tuple[Tuple[float, float], ...] + ]: """ Return a tuple containing coordinates for the two polygons outlining the latency and execution time of the operation. @@ -418,11 +420,51 @@ class Operation(GraphComponent, SignalSourceProvider): @abstractmethod def get_io_coordinates( self, - ) -> Tuple[List[List[float]], List[List[float]]]: + ) -> Tuple[ + Tuple[Tuple[float, float], ...], Tuple[Tuple[float, float], ...] + ]: """ Return a tuple containing coordinates for inputs and outputs, respectively. These maps to the polygons and are corresponding to a start time of 0 and height 1. + + See also + ======== + get_input_coordinates + get_output_coordinates + """ + raise NotImplementedError + + @abstractmethod + def get_input_coordinates( + self, + ) -> Tuple[Tuple[float, float], ...]: + """ + Return coordinates for inputs. + These maps to the polygons and are corresponding to a start time of 0 + and height 1. + + See also + ======== + get_io_coordinates + get_output_coordinates + """ + raise NotImplementedError + + @abstractmethod + def get_output_coordinates( + self, + ) -> Tuple[Tuple[float, float], ...]: + """ + Return coordinates for outputs. + These maps to the polygons and are corresponding to a start time of 0 + and height 1. + + See also + ======== + get_input_coordinates + get_io_coordinates + """ raise NotImplementedError @@ -915,7 +957,7 @@ class AbstractOperation(Operation, AbstractGraphComponent): return self.output(0) @property - def destination(self) -> OutputPort: + def destination(self) -> InputPort: if self.input_count != 1: diff = "more" if self.input_count > 1 else "less" raise TypeError( @@ -1044,25 +1086,29 @@ class AbstractOperation(Operation, AbstractGraphComponent): def get_plot_coordinates( self, - ) -> Tuple[List[List[float]], List[List[float]]]: + ) -> Tuple[ + Tuple[Tuple[float, float], ...], Tuple[Tuple[float, float], ...] + ]: # Doc-string inherited return ( self._get_plot_coordinates_for_latency(), self._get_plot_coordinates_for_execution_time(), ) - def _get_plot_coordinates_for_execution_time(self) -> List[List[float]]: + def _get_plot_coordinates_for_execution_time( + self, + ) -> Tuple[Tuple[float, float], ...]: # Always a rectangle, but easier if coordinates are returned execution_time = self._execution_time # Copy for type checking if execution_time is None: - return [] - return [ - [0, 0], - [0, 1], - [execution_time, 1], - [execution_time, 0], - [0, 0], - ] + return tuple() + return ( + (0, 0), + (0, 1), + (execution_time, 1), + (execution_time, 0), + (0, 0), + ) def _check_all_latencies_set(self): if any(val is None for val in self.latency_offsets.values()): @@ -1070,47 +1116,58 @@ class AbstractOperation(Operation, AbstractGraphComponent): f"All latencies must be set: {self.latency_offsets}" ) - def _get_plot_coordinates_for_latency(self) -> List[List[float]]: + def _get_plot_coordinates_for_latency( + self, + ) -> Tuple[Tuple[float, float], ...]: self._check_all_latencies_set() # Points for latency polygon latency = [] # Remember starting point - start_point = [self.inputs[0].latency_offset, 0.0] + start_point = (self.inputs[0].latency_offset, 0.0) num_in = self.input_count latency.append(start_point) for k in range(1, num_in): - latency.append([self.inputs[k - 1].latency_offset, k / num_in]) - latency.append([self.inputs[k].latency_offset, k / num_in]) - latency.append([self.inputs[num_in - 1].latency_offset, 1]) + latency.append((self.inputs[k - 1].latency_offset, k / num_in)) + latency.append((self.inputs[k].latency_offset, k / num_in)) + latency.append((self.inputs[num_in - 1].latency_offset, 1)) num_out = self.output_count - latency.append([self.outputs[num_out - 1].latency_offset, 1]) + latency.append((self.outputs[num_out - 1].latency_offset, 1)) for k in reversed(range(1, num_out)): - latency.append([self.outputs[k].latency_offset, k / num_out]) - latency.append([self.outputs[k - 1].latency_offset, k / num_out]) - latency.append([self.outputs[0].latency_offset, 0.0]) + latency.append((self.outputs[k].latency_offset, k / num_out)) + latency.append((self.outputs[k - 1].latency_offset, k / num_out)) + latency.append((self.outputs[0].latency_offset, 0.0)) # Close the polygon latency.append(start_point) - return latency + return tuple(latency) - def get_io_coordinates( - self, - ) -> Tuple[List[List[float]], List[List[float]]]: - # Doc-string inherited + def get_input_coordinates(self) -> Tuple[Tuple[float, float], ...]: + # doc-string inherited self._check_all_latencies_set() - input_coordinates = [ - [ + return tuple( + ( self.inputs[k].latency_offset, (1 + 2 * k) / (2 * len(self.inputs)), - ] + ) for k in range(len(self.inputs)) - ] - output_coordinates = [ - [ + ) + + def get_output_coordinates(self) -> Tuple[Tuple[float, float], ...]: + # doc-string inherited + self._check_all_latencies_set() + return tuple( + ( self.outputs[k].latency_offset, (1 + 2 * k) / (2 * len(self.outputs)), - ] + ) for k in range(len(self.outputs)) - ] - return input_coordinates, output_coordinates + ) + + def get_io_coordinates( + self, + ) -> Tuple[ + Tuple[Tuple[float, float], ...], Tuple[Tuple[float, float], ...] + ]: + # Doc-string inherited + return self.get_input_coordinates(), self.get_output_coordinates() diff --git a/b_asic/schedule.py b/b_asic/schedule.py index c64831e5fdef76625fa62ca0a74d86cfef64b46c..9ff02aa12d4191ec87f21c2976f44ac4728adc16 100644 --- a/b_asic/schedule.py +++ b/b_asic/schedule.py @@ -745,7 +745,7 @@ class Schedule: for graph_id, op_start_time in self._start_times.items(): op = self._sfg.find_by_id(graph_id) - _, out_coordinates = op.get_io_coordinates() + out_coordinates = op.get_output_coordinates() source_y_pos = self._get_y_position( graph_id, operation_gap=operation_gap ) @@ -759,11 +759,8 @@ class Schedule: destination_y_pos = self._get_y_position( destination_op.graph_id, operation_gap=operation_gap ) - ( - destination_in_coordinates, - _, - ) = ( - output_signal.destination.operation.get_io_coordinates() + destination_in_coordinates = ( + output_signal.destination.operation.get_input_coordinates() ) _draw_offset_arrow( out_coordinates[output_port.index], diff --git a/b_asic/signal.py b/b_asic/signal.py index e8aacbfc4a68ee9db22026e1b3ae2cdf302a2c1e..06dc2cf05ee79c9af3946d6779b75a5924e3f2be 100644 --- a/b_asic/signal.py +++ b/b_asic/signal.py @@ -47,10 +47,8 @@ class Signal(AbstractGraphComponent): def __init__( self, - source: Optional[Union["OutputPort", "Signal", "Operation"]] = None, - destination: Optional[ - Union["InputPort", "Signal", "Operation"] - ] = None, + source: Optional["OutputPort"] = None, + destination: Optional["InputPort"] = None, bits: Optional[int] = None, name: Name = Name(""), ): @@ -104,7 +102,10 @@ class Signal(AbstractGraphComponent): If Operation, it must have a single output, otherwise a TypeError is raised. That output is used to extract the OutputPort. """ - if hasattr(source, "source"): + # import here to avoid cyclic imports + from b_asic.operation import Operation + + if isinstance(source, (Signal, Operation)): # Signal or Operation source = source.source diff --git a/b_asic/special_operations.py b/b_asic/special_operations.py index ba587580d0643efa580cb0a3241a0f2d6fa59a2d..c0609dd595296483a894271114c279dd21cfde8e 100644 --- a/b_asic/special_operations.py +++ b/b_asic/special_operations.py @@ -57,32 +57,36 @@ class Input(AbstractOperation): def get_plot_coordinates( self, - ) -> Tuple[List[List[float]], List[List[float]]]: + ) -> Tuple[ + Tuple[Tuple[float, float], ...], Tuple[Tuple[float, float], ...] + ]: # Doc-string inherited return ( - [ - [-0.5, 0], - [-0.5, 1], - [-0.25, 1], - [0, 0.5], - [-0.25, 0], - [-0.5, 0], - ], - [ - [-0.5, 0], - [-0.5, 1], - [-0.25, 1], - [0, 0.5], - [-0.25, 0], - [-0.5, 0], - ], + ( + (-0.5, 0), + (-0.5, 1), + (-0.25, 1), + (0, 0.5), + (-0.25, 0), + (-0.5, 0), + ), + ( + (-0.5, 0), + (-0.5, 1), + (-0.25, 1), + (0, 0.5), + (-0.25, 0), + (-0.5, 0), + ), ) - def get_io_coordinates( - self, - ) -> Tuple[List[List[float]], List[List[float]]]: - # Doc-string inherited - return ([], [[0, 0.5]]) + def get_input_coordinates(self) -> Tuple[Tuple[float, float], ...]: + # doc-string inherited + return tuple() + + def get_output_coordinates(self) -> Tuple[Tuple[float, float], ...]: + # doc-string inherited + return ((0, 0.5),) class Output(AbstractOperation): @@ -119,18 +123,22 @@ class Output(AbstractOperation): def get_plot_coordinates( self, - ) -> Tuple[List[List[float]], List[List[float]]]: + ) -> Tuple[ + Tuple[Tuple[float, float], ...], Tuple[Tuple[float, float], ...] + ]: # Doc-string inherited return ( - [[0, 0], [0, 1], [0.25, 1], [0.5, 0.5], [0.25, 0], [0, 0]], - [[0, 0], [0, 1], [0.25, 1], [0.5, 0.5], [0.25, 0], [0, 0]], + ((0, 0), (0, 1), (0.25, 1), (0.5, 0.5), (0.25, 0), (0, 0)), + ((0, 0), (0, 1), (0.25, 1), (0.5, 0.5), (0.25, 0), (0, 0)), ) - def get_io_coordinates( - self, - ) -> Tuple[List[List[float]], List[List[float]]]: - # Doc-string inherited - return ([[0, 0.5]], []) + def get_input_coordinates(self) -> Tuple[Tuple[float, float], ...]: + # doc-string inherited + return ((0, 0.5),) + + def get_output_coordinates(self) -> Tuple[Tuple[float, float], ...]: + # doc-string inherited + return tuple() class Delay(AbstractOperation): diff --git a/test/test_operation.py b/test/test_operation.py index 8f2dbd0afba1e767e6fd8dfdc465af8cd3f6f41b..ec2fe26e577be29a8757fbe3a15414d86d4d2853 100644 --- a/test/test_operation.py +++ b/test/test_operation.py @@ -270,8 +270,8 @@ class TestPlotCoordinates: cmult.set_latency(3) lat, exe = cmult.get_plot_coordinates() - assert lat == [[0, 0], [0, 1], [3, 1], [3, 0], [0, 0]] - assert exe == [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]] + assert lat == ((0, 0), (0, 1), (3, 1), (3, 0), (0, 0)) + assert exe == ((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)) def test_complicated_case(self): bfly = Butterfly( @@ -280,18 +280,18 @@ class TestPlotCoordinates: bfly.execution_time = 7 lat, exe = bfly.get_plot_coordinates() - assert lat == [ - [2, 0], - [2, 0.5], - [3, 0.5], - [3, 1], - [10, 1], - [10, 0.5], - [5, 0.5], - [5, 0], - [2, 0], - ] - assert exe == [[0, 0], [0, 1], [7, 1], [7, 0], [0, 0]] + assert lat == ( + (2, 0), + (2, 0.5), + (3, 0.5), + (3, 1), + (10, 1), + (10, 0.5), + (5, 0.5), + (5, 0), + (2, 0), + ) + assert exe == ((0, 0), (0, 1), (7, 1), (7, 0), (0, 0)) class TestIOCoordinates: @@ -301,8 +301,8 @@ class TestIOCoordinates: cmult.set_latency(3) i_c, o_c = cmult.get_io_coordinates() - assert i_c == [[0, 0.5]] - assert o_c == [[3, 0.5]] + assert i_c == ((0, 0.5),) + assert o_c == ((3, 0.5),) def test_complicated_case(self): bfly = Butterfly( @@ -311,8 +311,15 @@ class TestIOCoordinates: bfly.execution_time = 7 i_c, o_c = bfly.get_io_coordinates() - assert i_c == [[2, 0.25], [3, 0.75]] - assert o_c == [[5, 0.25], [10, 0.75]] + assert i_c == ((2, 0.25), (3, 0.75)) + assert o_c == ((5, 0.25), (10, 0.75)) + + def test_io_coordinates_error(self): + bfly = Butterfly() + + bfly.set_latency_offsets({"in0": 3, "out1": 5}) + with pytest.raises(ValueError, match="All latencies must be set:"): + bfly.get_io_coordinates() class TestSplit: