From afb4d8cb1a174011029af9f8fe085a0fa828ad10 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson <oscar.gustafsson@gmail.com> Date: Sun, 4 Jun 2023 14:20:24 +0200 Subject: [PATCH] Test schedule and some fixes --- b_asic/schedule.py | 40 +++++----- test/test_schedule.py | 182 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 193 insertions(+), 29 deletions(-) diff --git a/b_asic/schedule.py b/b_asic/schedule.py index a842a8b3..2603afcb 100644 --- a/b_asic/schedule.py +++ b/b_asic/schedule.py @@ -152,7 +152,7 @@ class Schedule: The graph id of the operation to get the start time for. """ if graph_id not in self._start_times: - raise ValueError(f"No operation with graph_id {graph_id} in schedule") + raise ValueError(f"No operation with graph_id {graph_id!r} in schedule") return self._start_times[graph_id] def get_max_end_time(self) -> int: @@ -188,7 +188,7 @@ class Schedule: slacks """ if graph_id not in self._start_times: - raise ValueError(f"No operation with graph_id {graph_id} in schedule") + raise ValueError(f"No operation with graph_id {graph_id!r} in schedule") output_slacks = self._forward_slacks(graph_id) return cast( int, @@ -256,7 +256,7 @@ class Schedule: slacks """ if graph_id not in self._start_times: - raise ValueError(f"No operation with graph_id {graph_id} in schedule") + raise ValueError(f"No operation with graph_id {graph_id!r} in schedule") input_slacks = self._backward_slacks(graph_id) return cast( int, @@ -322,7 +322,7 @@ class Schedule: forward_slack """ if graph_id not in self._start_times: - raise ValueError(f"No operation with graph_id {graph_id} in schedule") + raise ValueError(f"No operation with graph_id {graph_id!r} in schedule") return self.backward_slack(graph_id), self.forward_slack(graph_id) def print_slacks(self, order: int = 0) -> None: @@ -614,12 +614,14 @@ class Schedule: The time to move. If positive move forward, if negative move backward. """ if graph_id not in self._start_times: - raise ValueError(f"No operation with graph_id {graph_id} in schedule") + raise ValueError(f"No operation with graph_id {graph_id!r} in schedule") + if time == 0: + return self (backward_slack, forward_slack) = self.slacks(graph_id) if not -backward_slack <= time <= forward_slack: raise ValueError( - f"Operation {graph_id} got incorrect move: {time}. Must be" + f"Operation {graph_id!r} got incorrect move: {time}. Must be" f" between {-backward_slack} and {forward_slack}." ) @@ -685,7 +687,6 @@ class Schedule: ): new_start = self._schedule_time self._laps[op.input(0).signals[0].graph_id] -= 1 - print(f"Moved {graph_id}") # Set new start time self._start_times[graph_id] = new_start return self @@ -698,22 +699,21 @@ class Schedule: schedule.move_operation(graph_id, schedule.forward_slack(graph_id)) - but Outputs will only move to the end of the schedule. + but operations with no succeeding operation (Outputs) will only move to the end + of the schedule. Parameters ---------- graph_id : GraphID The graph id of the operation to move. """ - op = self._sfg.find_by_id(graph_id) - if op is None: - raise ValueError(f"No operation with graph_id {graph_id!r} in schedule") - if isinstance(op, Output): + forward_slack = self.forward_slack(graph_id) + if forward_slack == sys.maxsize: self.move_operation( graph_id, self.schedule_time - self._start_times[graph_id] ) else: - self.move_operation(graph_id, self.forward_slack(graph_id)) + self.move_operation(graph_id, forward_slack) return self def move_operation_asap(self, graph_id: GraphID) -> "Schedule": @@ -724,20 +724,19 @@ class Schedule: schedule.move_operation(graph_id, -schedule.backward_slack(graph_id)) - but Inputs will only move to the start of the schedule. + but operations that do not have a preceeding operation (Inputs and Constants) + will only move to the start of the schedule. Parameters ---------- graph_id : GraphID The graph id of the operation to move. """ - op = self._sfg.find_by_id(graph_id) - if op is None: - raise ValueError(f"No operation with graph_id {graph_id!r} in schedule") - if isinstance(op, Input): + backward_slack = self.backward_slack(graph_id) + if backward_slack == sys.maxsize: self.move_operation(graph_id, -self._start_times[graph_id]) else: - self.move_operation(graph_id, -self.backward_slack(graph_id)) + self.move_operation(graph_id, -backward_slack) return self def _remove_delays_no_laps(self) -> None: @@ -789,8 +788,7 @@ class Schedule: precedence_list = self._sfg.get_precedence_list() if len(precedence_list) < 2: - print("Empty signal flow graph cannot be scheduled.") - return + raise ValueError("Empty signal flow graph cannot be scheduled.") non_schedulable_ops = set() for outport in precedence_list[0]: diff --git a/test/test_schedule.py b/test/test_schedule.py index c9e6c038..c7305ecb 100644 --- a/test/test_schedule.py +++ b/test/test_schedule.py @@ -27,15 +27,17 @@ class TestInit: } assert schedule.schedule_time == 9 + with pytest.raises( + ValueError, match="No operation with graph_id 'foo' in schedule" + ): + schedule.start_time_of_operation("foo") + def test_complicated_single_outputs_normal_latency(self, precedence_sfg_delays): precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 4) precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3) schedule = Schedule(precedence_sfg_delays, algorithm="ASAP") - for op in schedule._sfg.get_operations_topological_order(): - print(op.latency_offsets) - start_times_names = {} for op_id, start_time in schedule._start_times.items(): op_name = precedence_sfg_delays.find_by_id(op_id).name @@ -66,9 +68,6 @@ class TestInit: schedule = Schedule(precedence_sfg_delays, algorithm="ALAP") - for op in schedule._sfg.get_operations_topological_order(): - print(op.latency_offsets) - start_times_names = {} for op_id in schedule.start_times: op_name = precedence_sfg_delays.find_by_id(op_id).name @@ -91,6 +90,44 @@ class TestInit: } assert schedule.schedule_time == 21 + def test_complicated_single_outputs_normal_latency_alap_with_schedule_time( + self, precedence_sfg_delays + ): + precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 4) + precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3) + + schedule = Schedule(precedence_sfg_delays, schedule_time=25, algorithm="ALAP") + + start_times_names = {} + for op_id in schedule.start_times: + op_name = precedence_sfg_delays.find_by_id(op_id).name + start_times_names[op_name] = schedule.start_time_of_operation(op_id) + + assert start_times_names == { + "IN1": 8, + "C0": 8, + "B1": 4, + "B2": 4, + "ADD2": 7, + "ADD1": 11, + "Q1": 15, + "A0": 18, + "A1": 14, + "A2": 14, + "ADD3": 17, + "ADD4": 21, + "OUT1": 25, + } + assert schedule.schedule_time == 25 + + def test_complicated_single_outputs_normal_latency_alap_too_short_schedule_time( + self, precedence_sfg_delays + ): + precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 4) + precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3) + with pytest.raises(ValueError, match="Too short schedule time. Minimum is 21."): + Schedule(precedence_sfg_delays, schedule_time=19, algorithm="ALAP") + def test_complicated_single_outputs_normal_latency_from_fixture( self, secondorder_iir_schedule ): @@ -247,6 +284,74 @@ class TestSlacks: precedence_sfg_delays.find_by_name("A2")[0].graph_id ) == (16, 0) + def test_print_slacks(self, capsys, precedence_sfg_delays): + precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 1) + precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3) + + schedule = Schedule(precedence_sfg_delays, algorithm="ASAP") + schedule.print_slacks() + captured = capsys.readouterr() + assert captured.out == """Graph ID | Backward | Forward +---------|----------|--------- +add1 | 0 | 0 +add2 | 0 | 0 +add3 | 0 | 0 +add4 | 0 | 7 +cmul1 | 0 | 1 +cmul2 | 0 | 0 +cmul3 | 0 | 0 +cmul4 | 4 | 0 +cmul5 | 16 | 0 +cmul6 | 16 | 0 +cmul7 | 4 | 0 +in1 | oo | 0 +out1 | 0 | oo +""" + assert captured.err == "" + + def test_print_slacks_sorting(self, capsys, precedence_sfg_delays): + precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 1) + precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3) + + schedule = Schedule(precedence_sfg_delays, algorithm="ASAP") + schedule.print_slacks(1) + captured = capsys.readouterr() + assert captured.out == """Graph ID | Backward | Forward +---------|----------|--------- +cmul1 | 0 | 1 +add1 | 0 | 0 +add2 | 0 | 0 +cmul2 | 0 | 0 +cmul3 | 0 | 0 +add4 | 0 | 7 +add3 | 0 | 0 +out1 | 0 | oo +cmul4 | 4 | 0 +cmul7 | 4 | 0 +cmul5 | 16 | 0 +cmul6 | 16 | 0 +in1 | oo | 0 +""" + assert captured.err == "" + + def test_slacks_errors(self, precedence_sfg_delays): + precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 1) + precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3) + + schedule = Schedule(precedence_sfg_delays, algorithm="ASAP") + with pytest.raises( + ValueError, match="No operation with graph_id 'foo' in schedule" + ): + schedule.forward_slack("foo") + with pytest.raises( + ValueError, match="No operation with graph_id 'foo' in schedule" + ): + schedule.backward_slack("foo") + with pytest.raises( + ValueError, match="No operation with graph_id 'foo' in schedule" + ): + schedule.slacks("foo") + class TestRescheduling: def test_move_operation(self, precedence_sfg_delays): @@ -281,6 +386,11 @@ class TestRescheduling: "OUT1": 21, } + with pytest.raises( + ValueError, match="No operation with graph_id 'foo' in schedule" + ): + schedule.move_operation("foo", 0) + def test_move_operation_slack_after_rescheduling(self, precedence_sfg_delays): precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 1) precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3) @@ -310,7 +420,7 @@ class TestRescheduling: schedule = Schedule(precedence_sfg_delays, algorithm="ASAP") with pytest.raises( ValueError, - match="Operation add4 got incorrect move: -4. Must be between 0 and 7.", + match="Operation 'add4' got incorrect move: -4. Must be between 0 and 7.", ): schedule.move_operation( precedence_sfg_delays.find_by_name("ADD3")[0].graph_id, -4 @@ -323,12 +433,35 @@ class TestRescheduling: schedule = Schedule(precedence_sfg_delays, algorithm="ASAP") with pytest.raises( ValueError, - match="Operation add4 got incorrect move: 10. Must be between 0 and 7.", + match="Operation 'add4' got incorrect move: 10. Must be between 0 and 7.", ): schedule.move_operation( precedence_sfg_delays.find_by_name("ADD3")[0].graph_id, 10 ) + def test_move_operation_asap(self, precedence_sfg_delays): + precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 1) + precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3) + + schedule = Schedule(precedence_sfg_delays, algorithm="ASAP") + assert schedule.backward_slack('cmul6') == 16 + assert schedule.forward_slack('cmul6') == 0 + schedule.move_operation_asap('cmul6') + assert schedule.start_time_of_operation('in1') == 0 + assert schedule.laps['cmul6'] == 0 + assert schedule.backward_slack('cmul6') == 0 + assert schedule.forward_slack('cmul6') == 16 + + def test_move_input_asap_does_not_mess_up_laps(self, precedence_sfg_delays): + precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 1) + precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3) + + schedule = Schedule(precedence_sfg_delays, algorithm="ASAP") + old_laps = schedule.laps['in1'] + schedule.move_operation_asap('in1') + assert schedule.start_time_of_operation('in1') == 0 + assert schedule.laps['in1'] == old_laps + def test_move_operation_acc(self): in0 = Input() d = Delay() @@ -594,6 +727,24 @@ class TestErrors: ): Schedule(sfg_simple_filter, algorithm="foo") + def test_no_sfg(self): + with pytest.raises(TypeError, match="An SFG must be provided"): + Schedule(1) + + def test_provided_no_start_times(self, sfg_simple_filter): + sfg_simple_filter.set_latency_of_type(Addition.type_name(), 1) + sfg_simple_filter.set_latency_of_type(ConstantMultiplication.type_name(), 2) + with pytest.raises( + ValueError, match="Must provide start_times when using 'provided'" + ): + Schedule(sfg_simple_filter, algorithm="provided") + + def test_provided_no_laps(self, sfg_simple_filter): + sfg_simple_filter.set_latency_of_type(Addition.type_name(), 1) + sfg_simple_filter.set_latency_of_type(ConstantMultiplication.type_name(), 2) + with pytest.raises(ValueError, match="Must provide laps when using 'provided'"): + Schedule(sfg_simple_filter, algorithm="provided", start_times={'in0': 0}) + class TestGetUsedTypeNames: def test_secondorder_iir_schedule(self, secondorder_iir_schedule): @@ -603,3 +754,18 @@ class TestGetUsedTypeNames: 'in', 'out', ] + + +class TestYLocations: + def test_provided_no_laps(self, sfg_simple_filter): + sfg_simple_filter.set_latency_of_type(Addition.type_name(), 1) + sfg_simple_filter.set_latency_of_type(ConstantMultiplication.type_name(), 2) + schedule = Schedule(sfg_simple_filter) + # Assign locations + schedule.show() + print(schedule._y_locations) + assert schedule._y_locations == {'in1': 0, 'cmul1': 1, 'add1': 2, 'out1': 3} + schedule.move_y_location('add1', 1, insert=True) + assert schedule._y_locations == {'in1': 0, 'cmul1': 2, 'add1': 1, 'out1': 3} + schedule.move_y_location('out1', 1) + assert schedule._y_locations == {'in1': 0, 'cmul1': 2, 'add1': 1, 'out1': 1} -- GitLab