From 7fa9e870ef845b38f3cf36b6478690df3de7df70 Mon Sep 17 00:00:00 2001 From: Simon Bjurek <simbj106@student.liu.se> Date: Thu, 20 Feb 2025 15:06:21 +0000 Subject: [PATCH] Added port constrain to list scheduler, added example and updated tests --- .gitlab-ci.yml | 2 +- b_asic/architecture.py | 7 + b_asic/core_schedulers.py | 9 +- b_asic/resources.py | 4 +- b_asic/scheduler.py | 107 +++-- .../auto_scheduling_with_custom_io_times.py | 13 +- examples/ldlt_matrix_inverse.py | 5 +- examples/memory_constrained_scheduling.py | 143 +++++++ test/integration/test_sfg_to_architecture.py | 157 ++++++++ .../test_draw_matrix_transposer_4.png | Bin .../test_draw_process_collection.png | Bin .../test_left_edge_cell_assignment.png | Bin .../test_max_min_lifetime_bar_plot.png | Bin .../test__get_figure_no_execution_times.png | Bin test/{ => unit}/test_architecture.py | 2 +- test/{ => unit}/test_codegen.py | 0 test/{ => unit}/test_core_operations.py | 0 test/{ => unit}/test_core_schedulers.py | 371 ++++++++++-------- test/{ => unit}/test_graph_id_generator.py | 0 test/{ => unit}/test_gui.py | 0 test/{ => unit}/test_gui/twotapfir.py | 0 test/{ => unit}/test_inputport.py | 0 test/{ => unit}/test_operation.py | 0 test/{ => unit}/test_outputport.py | 0 test/{ => unit}/test_process.py | 0 test/{ => unit}/test_quantization.py | 0 test/{ => unit}/test_resources.py | 0 test/{ => unit}/test_schedule.py | 0 test/{ => unit}/test_scheduler_gui.py | 0 test/{ => unit}/test_sfg.py | 0 test/{ => unit}/test_sfg_generators.py | 0 test/{ => unit}/test_signal.py | 0 test/{ => unit}/test_signal_generator.py | 0 test/{ => unit}/test_signal_generator/bad.csv | 0 .../test_signal_generator/input.csv | 10 +- test/{ => unit}/test_simulation.py | 0 test/{ => unit}/test_simulation_gui.py | 0 test/{ => unit}/test_utils.py | 0 38 files changed, 615 insertions(+), 215 deletions(-) create mode 100644 examples/memory_constrained_scheduling.py create mode 100644 test/integration/test_sfg_to_architecture.py rename test/{ => unit}/baseline_images/test_resources/test_draw_matrix_transposer_4.png (100%) rename test/{ => unit}/baseline_images/test_resources/test_draw_process_collection.png (100%) rename test/{ => unit}/baseline_images/test_resources/test_left_edge_cell_assignment.png (100%) rename test/{ => unit}/baseline_images/test_resources/test_max_min_lifetime_bar_plot.png (100%) rename test/{ => unit}/baseline_images/test_schedule/test__get_figure_no_execution_times.png (100%) rename test/{ => unit}/test_architecture.py (99%) rename test/{ => unit}/test_codegen.py (100%) rename test/{ => unit}/test_core_operations.py (100%) rename test/{ => unit}/test_core_schedulers.py (79%) rename test/{ => unit}/test_graph_id_generator.py (100%) rename test/{ => unit}/test_gui.py (100%) rename test/{ => unit}/test_gui/twotapfir.py (100%) rename test/{ => unit}/test_inputport.py (100%) rename test/{ => unit}/test_operation.py (100%) rename test/{ => unit}/test_outputport.py (100%) rename test/{ => unit}/test_process.py (100%) rename test/{ => unit}/test_quantization.py (100%) rename test/{ => unit}/test_resources.py (100%) rename test/{ => unit}/test_schedule.py (100%) rename test/{ => unit}/test_scheduler_gui.py (100%) rename test/{ => unit}/test_sfg.py (100%) rename test/{ => unit}/test_sfg_generators.py (100%) rename test/{ => unit}/test_signal.py (100%) rename test/{ => unit}/test_signal_generator.py (100%) rename test/{ => unit}/test_signal_generator/bad.csv (100%) rename test/{ => unit}/test_signal_generator/input.csv (66%) rename test/{ => unit}/test_simulation.py (100%) rename test/{ => unit}/test_simulation_gui.py (100%) rename test/{ => unit}/test_utils.py (100%) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8c3439a7..4a822db9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,7 +23,7 @@ before_script: .run-test: stage: test script: - - pytest --cov=b_asic --cov-report=xml:cov.xml --cov-report=term --cov-branch --color=yes test --timeout=20 --durations=10 + - pytest --cov=b_asic --cov-report=xml:cov.xml --cov-report=term --cov-branch --color=yes test --timeout=30 --durations=10 # - lcov --capture --directory . --output-file coverage.info # - lcov --output-file coverage.info --extract coverage.info $PWD/src/'*' $PWD/b_asic/'*' # - lcov --list coverage.info diff --git a/b_asic/architecture.py b/b_asic/architecture.py index f55f7d8b..6e10d0f6 100644 --- a/b_asic/architecture.py +++ b/b_asic/architecture.py @@ -396,6 +396,13 @@ class ProcessingElement(Resource): assign: bool = True, ): super().__init__(process_collection=process_collection, entity_name=entity_name) + + if not isinstance(process_collection, ProcessCollection): + raise TypeError( + "Argument process_collection must be ProcessCollection, " + f"not {type(process_collection)}" + ) + if not all( isinstance(operator, OperatorProcess) for operator in process_collection.collection diff --git a/b_asic/core_schedulers.py b/b_asic/core_schedulers.py index 32cb23db..cce720e3 100644 --- a/b_asic/core_schedulers.py +++ b/b_asic/core_schedulers.py @@ -124,8 +124,7 @@ class EarliestDeadlineScheduler(ListScheduler): deadlines = {} for op_id, start_time in schedule_copy.start_times.items(): - if not op_id.startswith("in"): - deadlines[op_id] = start_time + schedule.sfg.find_by_id(op_id).latency + deadlines[op_id] = start_time + schedule.sfg.find_by_id(op_id).latency return sorted(deadlines, key=deadlines.get) @@ -141,7 +140,7 @@ class LeastSlackTimeScheduler(ListScheduler): sorted_ops = sorted( schedule_copy.start_times, key=schedule_copy.start_times.get ) - return [op for op in sorted_ops if not op.startswith("in")] + return sorted_ops class MaxFanOutScheduler(ListScheduler): @@ -157,7 +156,7 @@ class MaxFanOutScheduler(ListScheduler): fan_outs[op_id] = len(schedule.sfg.find_by_id(op_id).output_signals) sorted_ops = sorted(fan_outs, key=fan_outs.get, reverse=True) - return [op for op in sorted_ops if not op.startswith("in")] + return sorted_ops class HybridScheduler(ListScheduler): @@ -204,4 +203,4 @@ class HybridScheduler(ListScheduler): sorted_op_list = [pair[0] for pair in fan_out_sorted_items] - return [op for op in sorted_op_list if not op.startswith("in")] + return sorted_op_list diff --git a/b_asic/resources.py b/b_asic/resources.py index 97dffb68..597e7f29 100644 --- a/b_asic/resources.py +++ b/b_asic/resources.py @@ -877,7 +877,7 @@ class ProcessCollection: Parameters ---------- - heuristic : {'graph_color', 'left_edge'}, default: 'graph_color' + heuristic : {'graph_color', 'left_edge'}, default: 'left_edge' The heuristic used when splitting based on execution times. coloring_strategy : str, default: 'saturation_largest_first' @@ -919,7 +919,7 @@ class ProcessCollection: Parameters ---------- - heuristic : str, default: "graph_color" + heuristic : str, default: "left_edge" The heuristic used when splitting this :class:`ProcessCollection`. Valid options are: diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py index 1ecfbb2d..bf9a2474 100644 --- a/b_asic/scheduler.py +++ b/b_asic/scheduler.py @@ -1,3 +1,4 @@ +import sys from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Optional, cast @@ -9,6 +10,7 @@ from b_asic.types import TypeName if TYPE_CHECKING: from b_asic.operation import Operation from b_asic.schedule import Schedule + from b_asic.signal_flow_graph import SFG from b_asic.types import GraphID @@ -48,6 +50,8 @@ class ListScheduler(Scheduler, ABC): def __init__( self, max_resources: Optional[dict[TypeName, int]] = None, + max_concurrent_reads: Optional[int] = None, + max_concurrent_writes: Optional[int] = None, input_times: Optional[dict["GraphID", int]] = None, output_delta_times: Optional[dict["GraphID", int]] = None, cyclic: Optional[bool] = False, @@ -65,8 +69,11 @@ class ListScheduler(Scheduler, ABC): else: self._max_resources = {} - self._input_times = input_times if input_times else {} - self._output_delta_times = output_delta_times if output_delta_times else {} + self._max_concurrent_reads = max_concurrent_reads or sys.maxsize + self._max_concurrent_writes = max_concurrent_writes or sys.maxsize + + self._input_times = input_times or {} + self._output_delta_times = output_delta_times or {} def apply_scheduling(self, schedule: "Schedule") -> None: """Applies the scheduling algorithm on the given Schedule. @@ -77,60 +84,83 @@ class ListScheduler(Scheduler, ABC): Schedule to apply the scheduling algorithm on. """ sfg = schedule.sfg - start_times = schedule.start_times used_resources_ready_times = {} remaining_resources = self._max_resources.copy() + if Input.type_name() not in remaining_resources: + remaining_resources[Input.type_name()] = 1 + if Output.type_name() not in remaining_resources: + remaining_resources[Output.type_name()] = 1 + sorted_operations = self._get_sorted_operations(schedule) + schedule.start_times = {} + + remaining_reads = self._max_concurrent_reads + # initial input placement if self._input_times: for input_id in self._input_times: - start_times[input_id] = self._input_times[input_id] - - for input_op in sfg.find_by_type_name(Input.type_name()): - if input_op.graph_id not in self._input_times: - start_times[input_op.graph_id] = 0 + schedule.start_times[input_id] = self._input_times[input_id] + sorted_operations = [ + elem for elem in sorted_operations if not elem.startswith("in") + ] current_time = 0 + timeout_counter = 0 while sorted_operations: # generate the best schedulable candidate candidate = sfg.find_by_id(sorted_operations[0]) counter = 0 while not self._candidate_is_schedulable( - start_times, + schedule.start_times, + sfg, candidate, current_time, remaining_resources, + remaining_reads, + self._max_concurrent_writes, sorted_operations, ): if counter == len(sorted_operations): counter = 0 current_time += 1 + timeout_counter += 1 + + if timeout_counter > 10: + msg = "Algorithm did not schedule any operation for 10 time steps, try relaxing constraints." + raise TimeoutError(msg) + + remaining_reads = self._max_concurrent_reads + # update available operators for operation, ready_time in used_resources_ready_times.items(): if ready_time == current_time: remaining_resources[operation.type_name()] += 1 + else: candidate = sfg.find_by_id(sorted_operations[counter]) counter += 1 + timeout_counter = 0 # if the resource is constrained, update remaining resources if candidate.type_name() in remaining_resources: remaining_resources[candidate.type_name()] -= 1 - if candidate.execution_time: - used_resources_ready_times[candidate] = ( - current_time + candidate.execution_time - ) + if ( + candidate.type_name() == Input.type_name() + or candidate.type_name() == Output.type_name() + ): + used_resources_ready_times[candidate] = current_time + 1 else: used_resources_ready_times[candidate] = ( - current_time + candidate.latency + current_time + candidate.execution_time ) + remaining_reads -= candidate.input_count # schedule the best candidate to the current time sorted_operations.remove(candidate.graph_id) - start_times[candidate.graph_id] = current_time + schedule.start_times[candidate.graph_id] = current_time self._handle_outputs(schedule) @@ -138,12 +168,11 @@ class ListScheduler(Scheduler, ABC): max_start_time = max(schedule.start_times.values()) if current_time < max_start_time: current_time = max_start_time + current_time = max(current_time, schedule.get_max_end_time()) schedule.set_schedule_time(current_time) schedule.remove_delays() - self._handle_inputs(schedule) - # move all dont cares ALAP for dc_op in schedule.sfg.find_by_type_name(DontCare.type_name()): dc_op = cast(DontCare, dc_op) @@ -152,9 +181,12 @@ class ListScheduler(Scheduler, ABC): @staticmethod def _candidate_is_schedulable( start_times: dict["GraphID"], + sfg: "SFG", operation: "Operation", current_time: int, remaining_resources: dict["GraphID", int], + remaining_reads: int, + max_concurrent_writes: int, remaining_ops: list["GraphID"], ) -> bool: if ( @@ -163,20 +195,43 @@ class ListScheduler(Scheduler, ABC): ): return False + op_finish_time = current_time + operation.latency + future_ops = [ + sfg.find_by_id(item[0]) + for item in start_times.items() + if item[1] + sfg.find_by_id(item[0]).latency == op_finish_time + ] + + future_ops_writes = sum([op.input_count for op in future_ops]) + + if ( + not operation.graph_id.startswith("out") + and future_ops_writes >= max_concurrent_writes + ): + return False + + read_counter = 0 earliest_start_time = 0 for op_input in operation.inputs: source_op = op_input.signals[0].source.operation + if isinstance(source_op, Delay): + continue + source_op_graph_id = source_op.graph_id if source_op_graph_id in remaining_ops: return False - proceeding_op_start_time = start_times.get(source_op_graph_id) + if start_times[source_op_graph_id] != current_time - 1: + # not a direct connection -> memory read required + read_counter += 1 + + if read_counter > remaining_reads: + return False - if not isinstance(source_op, Delay): - earliest_start_time = max( - earliest_start_time, proceeding_op_start_time + source_op.latency - ) + proceeding_op_start_time = start_times.get(source_op_graph_id) + proceeding_op_finish_time = proceeding_op_start_time + source_op.latency + earliest_start_time = max(earliest_start_time, proceeding_op_finish_time) return earliest_start_time <= current_time @@ -184,17 +239,9 @@ class ListScheduler(Scheduler, ABC): def _get_sorted_operations(schedule: "Schedule") -> list["GraphID"]: raise NotImplementedError - def _handle_inputs(self, schedule: "Schedule") -> None: - for input_op in schedule.sfg.find_by_type_name(Input.type_name()): - input_op = cast(Input, input_op) - if input_op.graph_id not in self._input_times: - schedule.move_operation_alap(input_op.graph_id) - def _handle_outputs( self, schedule: "Schedule", non_schedulable_ops: Optional[list["GraphID"]] = [] ) -> None: - super()._handle_outputs(schedule, non_schedulable_ops) - schedule.set_schedule_time(schedule.get_max_end_time()) for output in schedule.sfg.find_by_type_name(Output.type_name()): diff --git a/examples/auto_scheduling_with_custom_io_times.py b/examples/auto_scheduling_with_custom_io_times.py index 6b6a90b6..8913bfd8 100644 --- a/examples/auto_scheduling_with_custom_io_times.py +++ b/examples/auto_scheduling_with_custom_io_times.py @@ -52,7 +52,12 @@ output_delta_times = { "out7": 5, } schedule = Schedule( - sfg, scheduler=HybridScheduler(resources, input_times, output_delta_times) + sfg, + scheduler=HybridScheduler( + resources, + input_times=input_times, + output_delta_times=output_delta_times, + ), ) schedule.show() @@ -70,7 +75,11 @@ output_delta_times = { } schedule = Schedule( sfg, - scheduler=HybridScheduler(resources, input_times, output_delta_times), + scheduler=HybridScheduler( + resources, + input_times=input_times, + output_delta_times=output_delta_times, + ), cyclic=True, ) schedule.show() diff --git a/examples/ldlt_matrix_inverse.py b/examples/ldlt_matrix_inverse.py index 4432e417..cf5961aa 100644 --- a/examples/ldlt_matrix_inverse.py +++ b/examples/ldlt_matrix_inverse.py @@ -84,7 +84,9 @@ output_delta_times = { } schedule = Schedule( sfg, - scheduler=HybridScheduler(resources, input_times, output_delta_times), + scheduler=HybridScheduler( + resources, input_times=input_times, output_delta_times=output_delta_times + ), cyclic=True, ) print("Scheduling time:", schedule.schedule_time) @@ -137,4 +139,3 @@ arch = Architecture( # %% arch -# schedule.edit() diff --git a/examples/memory_constrained_scheduling.py b/examples/memory_constrained_scheduling.py new file mode 100644 index 00000000..c1f81fae --- /dev/null +++ b/examples/memory_constrained_scheduling.py @@ -0,0 +1,143 @@ +""" +========================================= +Memory Constrained Scheduling +========================================= + +""" + +from b_asic.architecture import Architecture, Memory, ProcessingElement +from b_asic.core_operations import Butterfly, ConstantMultiplication +from b_asic.core_schedulers import ASAPScheduler, HybridScheduler +from b_asic.schedule import Schedule +from b_asic.sfg_generators import radix_2_dif_fft +from b_asic.special_operations import Input, Output + +sfg = radix_2_dif_fft(points=16) + +# %% +# The SFG is +sfg + +# %% +# Set latencies and execution times. +sfg.set_latency_of_type(Butterfly.type_name(), 3) +sfg.set_latency_of_type(ConstantMultiplication.type_name(), 2) +sfg.set_execution_time_of_type(Butterfly.type_name(), 1) +sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1) + +# # %% +# Generate an ASAP schedule for reference +schedule = Schedule(sfg, scheduler=ASAPScheduler()) +schedule.show() + +# %% +# Generate a PE constrained HybridSchedule +resources = {Butterfly.type_name(): 1, ConstantMultiplication.type_name(): 1} +schedule = Schedule(sfg, scheduler=HybridScheduler(resources)) +schedule.show() + +# %% Print the max number of read and write port accesses to non-direct memories +direct, mem_vars = schedule.get_memory_variables().split_on_length() +print("Max read ports:", mem_vars.read_ports_bound()) +print("Max write ports:", mem_vars.write_ports_bound()) + +# %% +operations = schedule.get_operations() +bfs = operations.get_by_type_name(Butterfly.type_name()) +bfs.show(title="Butterfly executions") +const_muls = operations.get_by_type_name(ConstantMultiplication.type_name()) +const_muls.show(title="ConstMul executions") +inputs = operations.get_by_type_name(Input.type_name()) +inputs.show(title="Input executions") +outputs = operations.get_by_type_name(Output.type_name()) +outputs.show(title="Output executions") + +bf_pe = ProcessingElement(bfs, entity_name="bf") +mul_pe = ProcessingElement(const_muls, entity_name="mul") + +pe_in = ProcessingElement(inputs, entity_name='input') +pe_out = ProcessingElement(outputs, entity_name='output') + +mem_vars = schedule.get_memory_variables() +mem_vars.show(title="All memory variables") +direct, mem_vars = mem_vars.split_on_length() +mem_vars.show(title="Non-zero time memory variables") +mem_vars_set = mem_vars.split_on_ports( + read_ports=1, write_ports=1, total_ports=2, heuristic="graph_color" +) + +# %% +memories = [] +for i, mem in enumerate(mem_vars_set): + memory = Memory(mem, memory_type="RAM", entity_name=f"memory{i}") + memories.append(memory) + mem.show(title=f"{memory.entity_name}") + memory.assign("graph_color") + memory.show_content(title=f"Assigned {memory.entity_name}") + +direct.show(title="Direct interconnects") + +# %% +arch = Architecture( + {bf_pe, mul_pe, pe_in, pe_out}, + memories, + direct_interconnects=direct, +) +arch + +# %% +# Generate another HybridSchedule but this time constrain the amount of reads and writes to reduce the amount of memories +resources = {Butterfly.type_name(): 1, ConstantMultiplication.type_name(): 1} +schedule = Schedule( + sfg, + scheduler=HybridScheduler( + resources, max_concurrent_reads=2, max_concurrent_writes=2 + ), +) +schedule.show() + +# %% Print the max number of read and write port accesses to non-direct memories +direct, mem_vars = schedule.get_memory_variables().split_on_length() +print("Max read ports:", mem_vars.read_ports_bound()) +print("Max write ports:", mem_vars.write_ports_bound()) + +# %% Proceed to construct PEs and plot executions and non-direct memory variables +operations = schedule.get_operations() +bfs = operations.get_by_type_name(Butterfly.type_name()) +bfs.show(title="Butterfly executions") +const_muls = operations.get_by_type_name(ConstantMultiplication.type_name()) +const_muls.show(title="ConstMul executions") +inputs = operations.get_by_type_name(Input.type_name()) +inputs.show(title="Input executions") +outputs = operations.get_by_type_name(Output.type_name()) +outputs.show(title="Output executions") + +bf_pe = ProcessingElement(bfs, entity_name="bf") +mul_pe = ProcessingElement(const_muls, entity_name="mul") + +pe_in = ProcessingElement(inputs, entity_name='input') +pe_out = ProcessingElement(outputs, entity_name='output') + +mem_vars.show(title="Non-zero time memory variables") +mem_vars_set = mem_vars.split_on_ports( + read_ports=1, write_ports=1, total_ports=2, heuristic="graph_color" +) + +# %% Allocate memories by graph coloring +memories = [] +for i, mem in enumerate(mem_vars_set): + memory = Memory(mem, memory_type="RAM", entity_name=f"memory{i}") + memories.append(memory) + mem.show(title=f"{memory.entity_name}") + memory.assign("graph_color") + memory.show_content(title=f"Assigned {memory.entity_name}") + +direct.show(title="Direct interconnects") + +# %% Synthesize the new architecture, now only using two memories but with data rate +arch = Architecture( + {bf_pe, mul_pe, pe_in, pe_out}, + memories, + direct_interconnects=direct, +) +arch diff --git a/test/integration/test_sfg_to_architecture.py b/test/integration/test_sfg_to_architecture.py new file mode 100644 index 00000000..c07e9766 --- /dev/null +++ b/test/integration/test_sfg_to_architecture.py @@ -0,0 +1,157 @@ +import pytest + +from b_asic.architecture import Architecture, Memory, ProcessingElement +from b_asic.core_operations import ( + MADS, + Butterfly, + ConstantMultiplication, + DontCare, + Reciprocal, +) +from b_asic.core_schedulers import ASAPScheduler, HybridScheduler +from b_asic.schedule import Schedule +from b_asic.sfg_generators import ldlt_matrix_inverse, radix_2_dif_fft +from b_asic.special_operations import Input, Output + + +def test_pe_constrained_schedule(): + sfg = ldlt_matrix_inverse(N=5) + + sfg.set_latency_of_type(MADS.type_name(), 3) + sfg.set_latency_of_type(Reciprocal.type_name(), 2) + sfg.set_execution_time_of_type(MADS.type_name(), 1) + sfg.set_execution_time_of_type(Reciprocal.type_name(), 1) + + resources = {MADS.type_name(): 2, Reciprocal.type_name(): 1} + + # Generate a schedule to ensure that schedule can be overwritten without bugs + schedule = Schedule(sfg, scheduler=ASAPScheduler()) + + schedule = Schedule(sfg, scheduler=HybridScheduler(resources)) + + direct, mem_vars = schedule.get_memory_variables().split_on_length() + assert mem_vars.read_ports_bound() <= 7 + assert mem_vars.write_ports_bound() <= 4 + + operations = schedule.get_operations() + + with pytest.raises( + TypeError, match="Different Operation types in ProcessCollection" + ): + ProcessingElement(operations) + + mads = operations.get_by_type_name(MADS.type_name()) + with pytest.raises( + ValueError, match="Cannot map ProcessCollection to single ProcessingElement" + ): + ProcessingElement(mads, entity_name="mad") + mads = mads.split_on_execution_time() + with pytest.raises( + TypeError, + match="Argument process_collection must be ProcessCollection, not <class 'list'>", + ): + ProcessingElement(mads, entity_name="mad") + + assert len(mads) == 2 + + reciprocals = operations.get_by_type_name(Reciprocal.type_name()) + dont_cares = operations.get_by_type_name(DontCare.type_name()) + inputs = operations.get_by_type_name(Input.type_name()) + outputs = operations.get_by_type_name(Output.type_name()) + + mads0 = ProcessingElement(mads[0], entity_name="mads0") + mads1 = ProcessingElement(mads[1], entity_name="mads1") + reciprocal_pe = ProcessingElement(reciprocals, entity_name="rec") + + dont_care_pe = ProcessingElement(dont_cares, entity_name="dc") + + pe_in = ProcessingElement(inputs, entity_name='input') + pe_out = ProcessingElement(outputs, entity_name='output') + + mem_vars_set = mem_vars.split_on_ports(read_ports=1, write_ports=1, total_ports=2) + memories = [] + for i, mem in enumerate(mem_vars_set): + memory = Memory(mem, memory_type="RAM", entity_name=f"memory{i}") + memories.append(memory) + memory.assign("graph_color") + + arch = Architecture( + {mads0, mads1, reciprocal_pe, dont_care_pe, pe_in, pe_out}, + memories, + direct_interconnects=direct, + ) + + assert len(arch.memories) == len(memories) + for i in range(len(memories)): + assert arch.memories[i] == memories[i] + + assert len(arch.processing_elements) == 6 + + assert arch.direct_interconnects == direct + + assert arch.schedule_time == schedule.schedule_time + + +def test_pe_and_memory_constrained_chedule(): + sfg = radix_2_dif_fft(points=16) + + sfg.set_latency_of_type(Butterfly.type_name(), 3) + sfg.set_latency_of_type(ConstantMultiplication.type_name(), 2) + sfg.set_execution_time_of_type(Butterfly.type_name(), 1) + sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1) + + # generate a schedule to ensure that schedule can be overwritten without bugs + schedule = Schedule(sfg, scheduler=ASAPScheduler()) + + # generate the real constrained schedule + resources = {Butterfly.type_name(): 1, ConstantMultiplication.type_name(): 1} + schedule = Schedule( + sfg, + scheduler=HybridScheduler( + resources, max_concurrent_reads=2, max_concurrent_writes=2 + ), + ) + + operations = schedule.get_operations() + bfs = operations.get_by_type_name(Butterfly.type_name()) + const_muls = operations.get_by_type_name(ConstantMultiplication.type_name()) + inputs = operations.get_by_type_name(Input.type_name()) + outputs = operations.get_by_type_name(Output.type_name()) + + bf_pe = ProcessingElement(bfs, entity_name="bf") + mul_pe = ProcessingElement(const_muls, entity_name="mul") + + pe_in = ProcessingElement(inputs, entity_name='input') + pe_out = ProcessingElement(outputs, entity_name='output') + + mem_vars = schedule.get_memory_variables() + direct, mem_vars = mem_vars.split_on_length() + mem_vars_set = mem_vars.split_on_ports( + read_ports=1, write_ports=1, total_ports=2, heuristic="graph_color" + ) + + mem_vars_set = mem_vars.split_on_ports( + read_ports=1, write_ports=1, total_ports=2, heuristic="graph_color" + ) + + memories = [] + for i, mem in enumerate(mem_vars_set): + memory = Memory(mem, memory_type="RAM", entity_name=f"memory{i}") + memories.append(memory) + memory.assign("graph_color") + + arch = Architecture( + {bf_pe, mul_pe, pe_in, pe_out}, + memories, + direct_interconnects=direct, + ) + + assert len(arch.memories) == 2 + assert arch.memories[0] == memories[0] + assert arch.memories[1] == memories[1] + + assert len(arch.processing_elements) == 4 + + assert arch.direct_interconnects == direct + + assert arch.schedule_time == schedule.schedule_time diff --git a/test/baseline_images/test_resources/test_draw_matrix_transposer_4.png b/test/unit/baseline_images/test_resources/test_draw_matrix_transposer_4.png similarity index 100% rename from test/baseline_images/test_resources/test_draw_matrix_transposer_4.png rename to test/unit/baseline_images/test_resources/test_draw_matrix_transposer_4.png diff --git a/test/baseline_images/test_resources/test_draw_process_collection.png b/test/unit/baseline_images/test_resources/test_draw_process_collection.png similarity index 100% rename from test/baseline_images/test_resources/test_draw_process_collection.png rename to test/unit/baseline_images/test_resources/test_draw_process_collection.png diff --git a/test/baseline_images/test_resources/test_left_edge_cell_assignment.png b/test/unit/baseline_images/test_resources/test_left_edge_cell_assignment.png similarity index 100% rename from test/baseline_images/test_resources/test_left_edge_cell_assignment.png rename to test/unit/baseline_images/test_resources/test_left_edge_cell_assignment.png diff --git a/test/baseline_images/test_resources/test_max_min_lifetime_bar_plot.png b/test/unit/baseline_images/test_resources/test_max_min_lifetime_bar_plot.png similarity index 100% rename from test/baseline_images/test_resources/test_max_min_lifetime_bar_plot.png rename to test/unit/baseline_images/test_resources/test_max_min_lifetime_bar_plot.png diff --git a/test/baseline_images/test_schedule/test__get_figure_no_execution_times.png b/test/unit/baseline_images/test_schedule/test__get_figure_no_execution_times.png similarity index 100% rename from test/baseline_images/test_schedule/test__get_figure_no_execution_times.png rename to test/unit/baseline_images/test_schedule/test__get_figure_no_execution_times.png diff --git a/test/test_architecture.py b/test/unit/test_architecture.py similarity index 99% rename from test/test_architecture.py rename to test/unit/test_architecture.py index 6b72bbdc..432f0652 100644 --- a/test/test_architecture.py +++ b/test/unit/test_architecture.py @@ -187,7 +187,7 @@ def test_move_process(schedule_direct_form_iir_lp_filter: Schedule): mvs = schedule_direct_form_iir_lp_filter.get_memory_variables() operations = schedule_direct_form_iir_lp_filter.get_operations() adders1, adders2 = operations.get_by_type_name(Addition.type_name()).split_on_ports( - total_ports=1 + heuristic="left_edge", total_ports=1 ) adders1 = [adders1] # Fake two PEs needed for the adders adders2 = [adders2] # Fake two PEs needed for the adders diff --git a/test/test_codegen.py b/test/unit/test_codegen.py similarity index 100% rename from test/test_codegen.py rename to test/unit/test_codegen.py diff --git a/test/test_core_operations.py b/test/unit/test_core_operations.py similarity index 100% rename from test/test_core_operations.py rename to test/unit/test_core_operations.py diff --git a/test/test_core_schedulers.py b/test/unit/test_core_schedulers.py similarity index 79% rename from test/test_core_schedulers.py rename to test/unit/test_core_schedulers.py index 29697486..755f5cc6 100644 --- a/test/test_core_schedulers.py +++ b/test/unit/test_core_schedulers.py @@ -1,3 +1,5 @@ +import sys + import pytest from b_asic.core_operations import ( @@ -21,6 +23,7 @@ from b_asic.sfg_generators import ( ldlt_matrix_inverse, radix_2_dif_fft, ) +from b_asic.special_operations import Input, Output class TestASAPScheduler: @@ -298,15 +301,20 @@ class TestEarliestDeadlineScheduler: sfg.set_latency_of_type(Addition.type_name(), 3) sfg.set_execution_time_of_type(Addition.type_name(), 1) - resources = {Addition.type_name(): 1, ConstantMultiplication.type_name(): 1} + resources = { + Addition.type_name(): 1, + ConstantMultiplication.type_name(): 1, + Input.type_name(): sys.maxsize, + Output.type_name(): sys.maxsize, + } schedule = Schedule( sfg, scheduler=EarliestDeadlineScheduler(max_resources=resources) ) assert schedule.start_times == { + "in0": 0, "cmul4": 0, "cmul3": 1, - "in0": 2, "cmul0": 2, "add1": 3, "cmul1": 3, @@ -318,67 +326,7 @@ class TestEarliestDeadlineScheduler: } assert schedule.schedule_time == 13 - def test_direct_form_2_iir_inf_resources_no_exec_time( - self, sfg_direct_form_iir_lp_filter - ): - sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5) - sfg_direct_form_iir_lp_filter.set_latency_of_type( - ConstantMultiplication.type_name(), 4 - ) - - schedule = Schedule( - sfg_direct_form_iir_lp_filter, scheduler=EarliestDeadlineScheduler() - ) - - # should be the same as for ASAP due to infinite resources, except for input - assert schedule.start_times == { - "in0": 9, - "cmul1": 0, - "cmul4": 0, - "cmul2": 0, - "cmul3": 0, - "add3": 4, - "add1": 4, - "add0": 9, - "cmul0": 14, - "add2": 18, - "out0": 23, - } - assert schedule.schedule_time == 23 - - def test_direct_form_2_iir_1_add_1_mul_no_exec_time( - self, sfg_direct_form_iir_lp_filter - ): - sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5) - sfg_direct_form_iir_lp_filter.set_latency_of_type( - ConstantMultiplication.type_name(), 4 - ) - - max_resources = {ConstantMultiplication.type_name(): 1, Addition.type_name(): 1} - - schedule = Schedule( - sfg_direct_form_iir_lp_filter, - scheduler=EarliestDeadlineScheduler(max_resources), - ) - assert schedule.start_times == { - "cmul4": 0, - "cmul3": 4, - "cmul1": 8, - "add1": 8, - "cmul2": 12, - "in0": 13, - "add0": 13, - "add3": 18, - "cmul0": 18, - "add2": 23, - "out0": 28, - } - - assert schedule.schedule_time == 28 - - def test_direct_form_2_iir_1_add_1_mul_exec_time_1( - self, sfg_direct_form_iir_lp_filter - ): + def test_direct_form_2_iir_1_add_1_mul(self, sfg_direct_form_iir_lp_filter): sfg_direct_form_iir_lp_filter.set_latency_of_type( ConstantMultiplication.type_name(), 3 ) @@ -390,19 +338,24 @@ class TestEarliestDeadlineScheduler: Addition.type_name(), 1 ) - max_resources = {ConstantMultiplication.type_name(): 1, Addition.type_name(): 1} + resources = { + Addition.type_name(): 1, + ConstantMultiplication.type_name(): 1, + Input.type_name(): sys.maxsize, + Output.type_name(): sys.maxsize, + } schedule = Schedule( sfg_direct_form_iir_lp_filter, - scheduler=EarliestDeadlineScheduler(max_resources), + scheduler=EarliestDeadlineScheduler(resources), ) assert schedule.start_times == { + "in0": 0, "cmul4": 0, "cmul3": 1, "cmul1": 2, "cmul2": 3, "add1": 4, - "in0": 6, "add0": 6, "add3": 7, "cmul0": 8, @@ -412,9 +365,7 @@ class TestEarliestDeadlineScheduler: assert schedule.schedule_time == 13 - def test_direct_form_2_iir_2_add_3_mul_exec_time_1( - self, sfg_direct_form_iir_lp_filter - ): + def test_direct_form_2_iir_2_add_3_mul(self, sfg_direct_form_iir_lp_filter): sfg_direct_form_iir_lp_filter.set_latency_of_type( ConstantMultiplication.type_name(), 3 ) @@ -426,20 +377,25 @@ class TestEarliestDeadlineScheduler: Addition.type_name(), 1 ) - max_resources = {ConstantMultiplication.type_name(): 3, Addition.type_name(): 2} + resources = { + Addition.type_name(): 2, + ConstantMultiplication.type_name(): 3, + Input.type_name(): sys.maxsize, + Output.type_name(): sys.maxsize, + } schedule = Schedule( sfg_direct_form_iir_lp_filter, - scheduler=EarliestDeadlineScheduler(max_resources), + scheduler=EarliestDeadlineScheduler(resources), ) assert schedule.start_times == { + "in0": 0, "cmul1": 0, "cmul4": 0, "cmul3": 0, "cmul2": 1, "add1": 3, "add3": 4, - "in0": 5, "add0": 5, "cmul0": 7, "add2": 10, @@ -456,26 +412,31 @@ class TestEarliestDeadlineScheduler: sfg.set_latency_of_type(Butterfly.type_name(), 1) sfg.set_execution_time_of_type(Butterfly.type_name(), 1) - resources = {Butterfly.type_name(): 2, ConstantMultiplication.type_name(): 2} + resources = { + Butterfly.type_name(): 2, + ConstantMultiplication.type_name(): 2, + Input.type_name(): sys.maxsize, + Output.type_name(): sys.maxsize, + } schedule = Schedule( sfg, scheduler=EarliestDeadlineScheduler(max_resources=resources) ) assert schedule.start_times == { + "in0": 0, "in1": 0, + "in2": 0, "in3": 0, + "in4": 0, "in5": 0, + "in6": 0, "in7": 0, "bfly6": 0, "bfly8": 0, - "in2": 1, - "in6": 1, "cmul2": 1, "cmul3": 1, "bfly11": 1, "bfly7": 1, - "in0": 2, - "in4": 2, "cmul0": 2, "bfly0": 2, "cmul4": 2, @@ -514,15 +475,20 @@ class TestLeastSlackTimeScheduler: sfg.set_latency_of_type(Addition.type_name(), 3) sfg.set_execution_time_of_type(Addition.type_name(), 1) - resources = {Addition.type_name(): 1, ConstantMultiplication.type_name(): 1} + resources = { + Addition.type_name(): 1, + ConstantMultiplication.type_name(): 1, + Input.type_name(): sys.maxsize, + Output.type_name(): sys.maxsize, + } schedule = Schedule( sfg, scheduler=LeastSlackTimeScheduler(max_resources=resources) ) assert schedule.start_times == { + "in0": 0, "cmul4": 0, "cmul3": 1, - "in0": 2, "cmul0": 2, "add1": 3, "cmul1": 3, @@ -534,67 +500,7 @@ class TestLeastSlackTimeScheduler: } assert schedule.schedule_time == 13 - def test_direct_form_2_iir_inf_resources_no_exec_time( - self, sfg_direct_form_iir_lp_filter - ): - sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5) - sfg_direct_form_iir_lp_filter.set_latency_of_type( - ConstantMultiplication.type_name(), 4 - ) - - schedule = Schedule( - sfg_direct_form_iir_lp_filter, scheduler=LeastSlackTimeScheduler() - ) - - # should be the same as for ASAP due to infinite resources, except for input - assert schedule.start_times == { - "in0": 9, - "cmul1": 0, - "cmul4": 0, - "cmul2": 0, - "cmul3": 0, - "add3": 4, - "add1": 4, - "add0": 9, - "cmul0": 14, - "add2": 18, - "out0": 23, - } - assert schedule.schedule_time == 23 - - def test_direct_form_2_iir_1_add_1_mul_no_exec_time( - self, sfg_direct_form_iir_lp_filter - ): - sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5) - sfg_direct_form_iir_lp_filter.set_latency_of_type( - ConstantMultiplication.type_name(), 4 - ) - - max_resources = {ConstantMultiplication.type_name(): 1, Addition.type_name(): 1} - - schedule = Schedule( - sfg_direct_form_iir_lp_filter, - scheduler=LeastSlackTimeScheduler(max_resources), - ) - assert schedule.start_times == { - "cmul4": 0, - "cmul3": 4, - "cmul1": 8, - "add1": 8, - "cmul2": 12, - "in0": 13, - "add0": 13, - "add3": 18, - "cmul0": 18, - "add2": 23, - "out0": 28, - } - - assert schedule.schedule_time == 28 - - def test_direct_form_2_iir_1_add_1_mul_exec_time_1( - self, sfg_direct_form_iir_lp_filter - ): + def test_direct_form_2_iir_1_add_1_mul(self, sfg_direct_form_iir_lp_filter): sfg_direct_form_iir_lp_filter.set_latency_of_type( ConstantMultiplication.type_name(), 3 ) @@ -606,19 +512,24 @@ class TestLeastSlackTimeScheduler: Addition.type_name(), 1 ) - max_resources = {ConstantMultiplication.type_name(): 1, Addition.type_name(): 1} + resources = { + Addition.type_name(): 1, + ConstantMultiplication.type_name(): 1, + Input.type_name(): sys.maxsize, + Output.type_name(): sys.maxsize, + } schedule = Schedule( sfg_direct_form_iir_lp_filter, - scheduler=LeastSlackTimeScheduler(max_resources), + scheduler=LeastSlackTimeScheduler(resources), ) assert schedule.start_times == { + "in0": 0, "cmul4": 0, "cmul3": 1, "cmul1": 2, "cmul2": 3, "add1": 4, - "in0": 6, "add0": 6, "add3": 7, "cmul0": 8, @@ -628,9 +539,7 @@ class TestLeastSlackTimeScheduler: assert schedule.schedule_time == 13 - def test_direct_form_2_iir_2_add_3_mul_exec_time_1( - self, sfg_direct_form_iir_lp_filter - ): + def test_direct_form_2_iir_2_add_3_mul(self, sfg_direct_form_iir_lp_filter): sfg_direct_form_iir_lp_filter.set_latency_of_type( ConstantMultiplication.type_name(), 3 ) @@ -642,20 +551,25 @@ class TestLeastSlackTimeScheduler: Addition.type_name(), 1 ) - max_resources = {ConstantMultiplication.type_name(): 3, Addition.type_name(): 2} + resources = { + Addition.type_name(): 2, + ConstantMultiplication.type_name(): 3, + Input.type_name(): sys.maxsize, + Output.type_name(): sys.maxsize, + } schedule = Schedule( sfg_direct_form_iir_lp_filter, - scheduler=LeastSlackTimeScheduler(max_resources), + scheduler=LeastSlackTimeScheduler(resources), ) assert schedule.start_times == { + "in0": 0, "cmul1": 0, "cmul4": 0, "cmul3": 0, "cmul2": 1, "add1": 3, "add3": 4, - "in0": 5, "add0": 5, "cmul0": 7, "add2": 10, @@ -672,26 +586,31 @@ class TestLeastSlackTimeScheduler: sfg.set_latency_of_type(Butterfly.type_name(), 1) sfg.set_execution_time_of_type(Butterfly.type_name(), 1) - resources = {Butterfly.type_name(): 2, ConstantMultiplication.type_name(): 2} + resources = { + Butterfly.type_name(): 2, + ConstantMultiplication.type_name(): 2, + Input.type_name(): sys.maxsize, + Output.type_name(): sys.maxsize, + } schedule = Schedule( sfg, scheduler=LeastSlackTimeScheduler(max_resources=resources) ) assert schedule.start_times == { + "in0": 0, "in1": 0, + "in2": 0, "in3": 0, + "in4": 0, "in5": 0, + "in6": 0, "in7": 0, "bfly6": 0, "bfly8": 0, - "in2": 1, - "in6": 1, "cmul2": 1, "cmul3": 1, "bfly11": 1, "bfly7": 1, - "in0": 2, - "in4": 2, "cmul0": 2, "bfly0": 2, "cmul4": 2, @@ -768,9 +687,9 @@ class TestHybridScheduler: schedule = Schedule(sfg, scheduler=HybridScheduler(max_resources=resources)) assert schedule.start_times == { + "in0": 0, "cmul4": 0, "cmul3": 1, - "in0": 2, "cmul0": 2, "add1": 3, "cmul1": 3, @@ -790,24 +709,29 @@ class TestHybridScheduler: sfg.set_latency_of_type(Butterfly.type_name(), 1) sfg.set_execution_time_of_type(Butterfly.type_name(), 1) - resources = {Butterfly.type_name(): 2, ConstantMultiplication.type_name(): 2} + resources = { + Butterfly.type_name(): 2, + ConstantMultiplication.type_name(): 2, + Input.type_name(): sys.maxsize, + Output.type_name(): sys.maxsize, + } schedule = Schedule(sfg, scheduler=HybridScheduler(max_resources=resources)) assert schedule.start_times == { + "in0": 0, "in1": 0, + "in2": 0, "in3": 0, + "in4": 0, "in5": 0, + "in6": 0, "in7": 0, "bfly6": 0, "bfly8": 0, - "in2": 1, - "in6": 1, "cmul2": 1, "cmul3": 1, "bfly11": 1, "bfly7": 1, - "in0": 2, - "in4": 2, "cmul0": 2, "bfly0": 2, "cmul4": 2, @@ -830,6 +754,59 @@ class TestHybridScheduler: } assert schedule.schedule_time == 7 + def test_radix_2_fft_8_points_one_output(self): + sfg = radix_2_dif_fft(points=8) + + sfg.set_latency_of_type(ConstantMultiplication.type_name(), 2) + sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1) + sfg.set_latency_of_type(Butterfly.type_name(), 1) + sfg.set_execution_time_of_type(Butterfly.type_name(), 1) + + resources = { + Butterfly.type_name(): 2, + ConstantMultiplication.type_name(): 2, + Input.type_name(): sys.maxsize, + Output.type_name(): 1, + } + schedule = Schedule(sfg, scheduler=HybridScheduler(max_resources=resources)) + + assert schedule.start_times == { + "in0": 0, + "in1": 0, + "in2": 0, + "in3": 0, + "in4": 0, + "in5": 0, + "in6": 0, + "in7": 0, + "bfly6": 0, + "bfly8": 0, + "cmul2": 1, + "cmul3": 1, + "bfly11": 1, + "bfly7": 1, + "cmul0": 2, + "bfly0": 2, + "cmul4": 2, + "bfly5": 3, + "bfly1": 3, + "cmul1": 4, + "bfly2": 4, + "bfly9": 4, + "bfly10": 5, + "bfly3": 5, + "out0": 5, + "bfly4": 6, + "out1": 6, + "out2": 12, + "out3": 11, + "out4": 10, + "out5": 9, + "out6": 8, + "out7": 7, + } + assert schedule.schedule_time == 12 + def test_radix_2_fft_8_points_specified_IO_times_cyclic(self): sfg = radix_2_dif_fft(points=8) @@ -838,7 +815,12 @@ class TestHybridScheduler: sfg.set_execution_time_of_type(Butterfly.type_name(), 1) sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1) - resources = {Butterfly.type_name(): 1, ConstantMultiplication.type_name(): 1} + resources = { + Butterfly.type_name(): 1, + ConstantMultiplication.type_name(): 1, + Input.type_name(): sys.maxsize, + Output.type_name(): sys.maxsize, + } input_times = { "in0": 0, "in1": 1, @@ -861,7 +843,9 @@ class TestHybridScheduler: } schedule = Schedule( sfg, - scheduler=HybridScheduler(resources, input_times, output_times), + scheduler=HybridScheduler( + resources, input_times=input_times, output_delta_times=output_times + ), cyclic=True, ) @@ -933,7 +917,9 @@ class TestHybridScheduler: } schedule = Schedule( sfg, - scheduler=HybridScheduler(resources, input_times, output_times), + scheduler=HybridScheduler( + resources, input_times=input_times, output_delta_times=output_times + ), cyclic=False, ) @@ -982,7 +968,12 @@ class TestHybridScheduler: sfg.set_execution_time_of_type(MADS.type_name(), 1) sfg.set_execution_time_of_type(Reciprocal.type_name(), 1) - resources = {MADS.type_name(): 1, Reciprocal.type_name(): 1} + resources = { + MADS.type_name(): 1, + Reciprocal.type_name(): 1, + Input.type_name(): sys.maxsize, + Output.type_name(): sys.maxsize, + } schedule = Schedule( sfg, scheduler=HybridScheduler(resources), @@ -990,11 +981,11 @@ class TestHybridScheduler: assert schedule.start_times == { "in0": 0, + "in1": 0, + "in2": 0, "rec0": 0, - "in1": 2, "dontcare1": 2, "mads0": 2, - "in2": 5, "mads3": 5, "rec1": 8, "dontcare0": 10, @@ -1027,7 +1018,9 @@ class TestHybridScheduler: } schedule = Schedule( sfg, - scheduler=HybridScheduler(resources, input_times, output_times), + scheduler=HybridScheduler( + resources, input_times=input_times, output_delta_times=output_times + ), cyclic=True, ) @@ -1078,3 +1071,47 @@ class TestHybridScheduler: resources = {MADS.type_name(): "test"} with pytest.raises(ValueError, match="max_resources value must be an integer."): Schedule(sfg, scheduler=HybridScheduler(resources)) + + def test_ldlt_inverse_3x3_read_and_write_constrained(self): + sfg = ldlt_matrix_inverse(N=3) + + sfg.set_latency_of_type(MADS.type_name(), 3) + sfg.set_latency_of_type(Reciprocal.type_name(), 2) + sfg.set_execution_time_of_type(MADS.type_name(), 1) + sfg.set_execution_time_of_type(Reciprocal.type_name(), 1) + + resources = {MADS.type_name(): 1, Reciprocal.type_name(): 1} + + schedule = Schedule( + sfg, + scheduler=HybridScheduler( + max_resources=resources, + max_concurrent_reads=3, + max_concurrent_writes=1, + ), + ) + + direct, mem_vars = schedule.get_memory_variables().split_on_length() + assert mem_vars.read_ports_bound() == 3 + assert mem_vars.write_ports_bound() == 1 + + def test_read_constrained_too_tight(self): + sfg = ldlt_matrix_inverse(N=2) + + sfg.set_latency_of_type(MADS.type_name(), 3) + sfg.set_latency_of_type(Reciprocal.type_name(), 2) + sfg.set_execution_time_of_type(MADS.type_name(), 1) + sfg.set_execution_time_of_type(Reciprocal.type_name(), 1) + + resources = {MADS.type_name(): 1, Reciprocal.type_name(): 1} + with pytest.raises( + TimeoutError, + match="Algorithm did not schedule any operation for 10 time steps, try relaxing constraints.", + ): + Schedule( + sfg, + scheduler=HybridScheduler( + max_resources=resources, + max_concurrent_reads=2, + ), + ) diff --git a/test/test_graph_id_generator.py b/test/unit/test_graph_id_generator.py similarity index 100% rename from test/test_graph_id_generator.py rename to test/unit/test_graph_id_generator.py diff --git a/test/test_gui.py b/test/unit/test_gui.py similarity index 100% rename from test/test_gui.py rename to test/unit/test_gui.py diff --git a/test/test_gui/twotapfir.py b/test/unit/test_gui/twotapfir.py similarity index 100% rename from test/test_gui/twotapfir.py rename to test/unit/test_gui/twotapfir.py diff --git a/test/test_inputport.py b/test/unit/test_inputport.py similarity index 100% rename from test/test_inputport.py rename to test/unit/test_inputport.py diff --git a/test/test_operation.py b/test/unit/test_operation.py similarity index 100% rename from test/test_operation.py rename to test/unit/test_operation.py diff --git a/test/test_outputport.py b/test/unit/test_outputport.py similarity index 100% rename from test/test_outputport.py rename to test/unit/test_outputport.py diff --git a/test/test_process.py b/test/unit/test_process.py similarity index 100% rename from test/test_process.py rename to test/unit/test_process.py diff --git a/test/test_quantization.py b/test/unit/test_quantization.py similarity index 100% rename from test/test_quantization.py rename to test/unit/test_quantization.py diff --git a/test/test_resources.py b/test/unit/test_resources.py similarity index 100% rename from test/test_resources.py rename to test/unit/test_resources.py diff --git a/test/test_schedule.py b/test/unit/test_schedule.py similarity index 100% rename from test/test_schedule.py rename to test/unit/test_schedule.py diff --git a/test/test_scheduler_gui.py b/test/unit/test_scheduler_gui.py similarity index 100% rename from test/test_scheduler_gui.py rename to test/unit/test_scheduler_gui.py diff --git a/test/test_sfg.py b/test/unit/test_sfg.py similarity index 100% rename from test/test_sfg.py rename to test/unit/test_sfg.py diff --git a/test/test_sfg_generators.py b/test/unit/test_sfg_generators.py similarity index 100% rename from test/test_sfg_generators.py rename to test/unit/test_sfg_generators.py diff --git a/test/test_signal.py b/test/unit/test_signal.py similarity index 100% rename from test/test_signal.py rename to test/unit/test_signal.py diff --git a/test/test_signal_generator.py b/test/unit/test_signal_generator.py similarity index 100% rename from test/test_signal_generator.py rename to test/unit/test_signal_generator.py diff --git a/test/test_signal_generator/bad.csv b/test/unit/test_signal_generator/bad.csv similarity index 100% rename from test/test_signal_generator/bad.csv rename to test/unit/test_signal_generator/bad.csv diff --git a/test/test_signal_generator/input.csv b/test/unit/test_signal_generator/input.csv similarity index 66% rename from test/test_signal_generator/input.csv rename to test/unit/test_signal_generator/input.csv index b0917c8e..64737476 100644 --- a/test/test_signal_generator/input.csv +++ b/test/unit/test_signal_generator/input.csv @@ -1,5 +1,5 @@ -0 -1 -0 -0 -0 +0 +1 +0 +0 +0 diff --git a/test/test_simulation.py b/test/unit/test_simulation.py similarity index 100% rename from test/test_simulation.py rename to test/unit/test_simulation.py diff --git a/test/test_simulation_gui.py b/test/unit/test_simulation_gui.py similarity index 100% rename from test/test_simulation_gui.py rename to test/unit/test_simulation_gui.py diff --git a/test/test_utils.py b/test/unit/test_utils.py similarity index 100% rename from test/test_utils.py rename to test/unit/test_utils.py -- GitLab