From 2724fa3d8a0a487948c78b762eb515c5c3c4827b Mon Sep 17 00:00:00 2001 From: Samuel Fagerlund <samfa392@student.liu.se> Date: Mon, 1 Jul 2024 11:32:14 +0200 Subject: [PATCH] Add tests --- b_asic/GUI/__init__.py | 1 + b_asic/GUI/port_button.py | 1 + b_asic/GUI/precedence_graph_window.py | 1 + b_asic/GUI/properties_window.py | 10 +++--- b_asic/GUI/select_sfg_window.py | 1 + b_asic/GUI/simulate_sfg_window.py | 1 + b_asic/__init__.py | 1 + b_asic/codegen/vhdl/entity.py | 1 + b_asic/core_operations.py | 10 ++++++ b_asic/gui_utils/color_button.py | 1 + b_asic/save_load_structure.py | 16 +++++---- b_asic/schedule.py | 15 ++++---- b_asic/scheduler_gui/__init__.py | 1 + b_asic/scheduler_gui/scheduler_event.py | 12 +++---- b_asic/scheduler_gui/signal_item.py | 1 - b_asic/sfg_generators.py | 1 + b_asic/signal.py | 1 + b_asic/signal_flow_graph.py | 19 +++++----- b_asic/special_operations.py | 1 + examples/introduction.py | 1 + examples/schedulingexample.py | 1 + examples/thirdorderblwdf.py | 1 + test/fixtures/operation_tree.py | 4 +-- test/test_gui/twotapfir.py | 15 ++------ test/test_operation.py | 1 + test/test_outputport.py | 1 + test/test_sfg.py | 46 +++++++++++++++++++++++++ 27 files changed, 112 insertions(+), 53 deletions(-) diff --git a/b_asic/GUI/__init__.py b/b_asic/GUI/__init__.py index 357ada54..89256ec9 100644 --- a/b_asic/GUI/__init__.py +++ b/b_asic/GUI/__init__.py @@ -2,6 +2,7 @@ Graphical user interface for B-ASIC. """ + from b_asic.GUI.main_window import start_editor __all__ = ['start_editor'] diff --git a/b_asic/GUI/port_button.py b/b_asic/GUI/port_button.py index 92f2696c..c90166d0 100644 --- a/b_asic/GUI/port_button.py +++ b/b_asic/GUI/port_button.py @@ -1,6 +1,7 @@ """ B-ASIC port button module. """ + from typing import TYPE_CHECKING from qtpy.QtCore import QMimeData, Qt, Signal diff --git a/b_asic/GUI/precedence_graph_window.py b/b_asic/GUI/precedence_graph_window.py index 090ca3d1..57365bf9 100644 --- a/b_asic/GUI/precedence_graph_window.py +++ b/b_asic/GUI/precedence_graph_window.py @@ -1,6 +1,7 @@ """ B-ASIC window to show precedence graph. """ + from qtpy.QtCore import Qt, Signal from qtpy.QtWidgets import ( QCheckBox, diff --git a/b_asic/GUI/properties_window.py b/b_asic/GUI/properties_window.py index cb6cda29..6babd698 100644 --- a/b_asic/GUI/properties_window.py +++ b/b_asic/GUI/properties_window.py @@ -162,10 +162,12 @@ class PropertiesWindow(QDialog): self.operation.operation.set_latency_offsets( { - port: float(latency_edit.text().replace(",", ".")) - if latency_edit.text() - and float(latency_edit.text().replace(",", ".")) > 0 - else None + port: ( + float(latency_edit.text().replace(",", ".")) + if latency_edit.text() + and float(latency_edit.text().replace(",", ".")) > 0 + else None + ) for port, latency_edit in self._latency_fields.items() } ) diff --git a/b_asic/GUI/select_sfg_window.py b/b_asic/GUI/select_sfg_window.py index 53118a4b..a3ac58bf 100644 --- a/b_asic/GUI/select_sfg_window.py +++ b/b_asic/GUI/select_sfg_window.py @@ -1,6 +1,7 @@ """ B-ASIC select SFG window. """ + from typing import TYPE_CHECKING from qtpy.QtCore import Qt, Signal diff --git a/b_asic/GUI/simulate_sfg_window.py b/b_asic/GUI/simulate_sfg_window.py index 2e27170e..29dc5c8e 100644 --- a/b_asic/GUI/simulate_sfg_window.py +++ b/b_asic/GUI/simulate_sfg_window.py @@ -1,6 +1,7 @@ """ B-ASIC window to simulate an SFG. """ + from typing import TYPE_CHECKING, Dict from qtpy.QtCore import Qt, Signal diff --git a/b_asic/__init__.py b/b_asic/__init__.py index f1fded40..fae7aec4 100644 --- a/b_asic/__init__.py +++ b/b_asic/__init__.py @@ -1,6 +1,7 @@ """B-ASIC - Better ASIC Toolbox. ASIC toolbox that simplifies circuit design and optimization. """ + # Python modules. from b_asic.core_operations import * from b_asic.graph_component import * diff --git a/b_asic/codegen/vhdl/entity.py b/b_asic/codegen/vhdl/entity.py index 4ccbd28d..c52f8584 100644 --- a/b_asic/codegen/vhdl/entity.py +++ b/b_asic/codegen/vhdl/entity.py @@ -1,6 +1,7 @@ """ Module for code generation of VHDL entity declarations """ + from typing import Set, TextIO from b_asic.codegen.vhdl import VHDL_TAB, write_lines diff --git a/b_asic/core_operations.py b/b_asic/core_operations.py index e731a480..4e44de1c 100644 --- a/b_asic/core_operations.py +++ b/b_asic/core_operations.py @@ -73,6 +73,7 @@ class Constant(AbstractOperation): def __str__(self) -> str: return f"{self.value}" + class Addition(AbstractOperation): """ Binary addition operation. @@ -240,6 +241,7 @@ class AddSub(AbstractOperation): ======== Addition, Subtraction """ + is_linear = True def __init__( @@ -317,6 +319,7 @@ class Multiplication(AbstractOperation): ======== ConstantMultiplication """ + is_swappable = True def __init__( @@ -450,6 +453,7 @@ class Min(AbstractOperation): ======== Max """ + is_swappable = True def __init__( @@ -515,6 +519,7 @@ class Max(AbstractOperation): ======== Min """ + is_swappable = True def __init__( @@ -729,6 +734,7 @@ class ConstantMultiplication(AbstractOperation): -------- Multiplication """ + is_linear = True def __init__( @@ -802,6 +808,7 @@ class Butterfly(AbstractOperation): execution_time : int, optional Operation execution time (time units before operator can be reused). """ + is_linear = True def __init__( @@ -865,6 +872,7 @@ class MAD(AbstractOperation): Multiplication Addition """ + is_swappable = True def __init__( @@ -922,6 +930,7 @@ class SymmetricTwoportAdaptor(AbstractOperation): y_1 & = & x_0 + \text{value}\times\left(x_1 - x_0\right) \end{eqnarray} """ + is_linear = True is_swappable = True @@ -1263,6 +1272,7 @@ class Shift(AbstractOperation): raise TypeError("value must be an int") self.set_param("value", value) + class Sink(AbstractOperation): r""" Sink operation. diff --git a/b_asic/gui_utils/color_button.py b/b_asic/gui_utils/color_button.py index 725ddda1..5f753e92 100644 --- a/b_asic/gui_utils/color_button.py +++ b/b_asic/gui_utils/color_button.py @@ -1,6 +1,7 @@ """ Qt button for use in preference dialogs, selecting color. """ + from qtpy.QtCore import Qt, Signal from qtpy.QtGui import QColor from qtpy.QtWidgets import QColorDialog, QPushButton diff --git a/b_asic/save_load_structure.py b/b_asic/save_load_structure.py index 1dccc40b..58497f4d 100644 --- a/b_asic/save_load_structure.py +++ b/b_asic/save_load_structure.py @@ -63,9 +63,11 @@ def sfg_to_python( if attr != "latency" and hasattr(comp, attr) } params = { - attr: getattr(comp, attr) - if not isinstance(getattr(comp, attr), str) - else f'"{getattr(comp, attr)}"' + attr: ( + getattr(comp, attr) + if not isinstance(getattr(comp, attr), str) + else f'"{getattr(comp, attr)}"' + ) for attr in params_filtered } params = {k: v for k, v in params.items() if v} @@ -153,9 +155,11 @@ def python_to_sfg(path: str) -> Tuple[SFG, Dict[str, Tuple[int, int]]]: exec(code, globals(), locals()) return ( - locals()["prop"]["name"] - if "prop" in locals() - else [v for k, v in locals().items() if isinstance(v, SFG)][0], + ( + locals()["prop"]["name"] + if "prop" in locals() + else [v for k, v in locals().items() if isinstance(v, SFG)][0] + ), locals()["positions"] if "positions" in locals() else {}, ) diff --git a/b_asic/schedule.py b/b_asic/schedule.py index 7fe533fa..4b34d580 100644 --- a/b_asic/schedule.py +++ b/b_asic/schedule.py @@ -526,9 +526,7 @@ class Schedule: """ self._sfg.set_execution_time_of_type(type_name, execution_time) - def set_latency_of_type( - self, type_name: TypeName, latency: int - ) -> None: + def set_latency_of_type(self, type_name: TypeName, latency: int) -> None: """ Set the latency of all operations with the given type name. @@ -542,20 +540,19 @@ class Schedule: """ passed = True for op in self._sfg.operations: - if type_name == op.type_name() or type_name == op.graph_id: + if type_name == op.type_name() or type_name == op.graph_id: change_in_latency = latency - op.latency if change_in_latency > (self.forward_slack(op.graph_id)): passed = False raise ValueError( f"Error: Can't increase latency for all components. Try increassing forward slack time by rescheduling. " - f"Error in: {op.graph_id}" + f"Error in: {op.graph_id}" ) break if change_in_latency < 0 or passed: - for op in self._sfg.operations: - if type_name == op.type_name() or type_name == op.graph_id: - cast(Operation, op).set_latency(latency) - + for op in self._sfg.operations: + if type_name == op.type_name() or type_name == op.graph_id: + cast(Operation, op).set_latency(latency) def move_y_location( self, graph_id: GraphID, new_y: int, insert: bool = False diff --git a/b_asic/scheduler_gui/__init__.py b/b_asic/scheduler_gui/__init__.py index fa401de5..65a54cad 100644 --- a/b_asic/scheduler_gui/__init__.py +++ b/b_asic/scheduler_gui/__init__.py @@ -3,6 +3,7 @@ B-ASIC Scheduler-gui Module. Graphical user interface for B-ASIC scheduler. """ + from b_asic.scheduler_gui.main_window import start_scheduler __all__ = ['start_scheduler'] diff --git a/b_asic/scheduler_gui/scheduler_event.py b/b_asic/scheduler_gui/scheduler_event.py index d11df346..6df2b679 100644 --- a/b_asic/scheduler_gui/scheduler_event.py +++ b/b_asic/scheduler_gui/scheduler_event.py @@ -69,12 +69,10 @@ class SchedulerEvent: # PyQt5 # Filters # ########### @overload - def installSceneEventFilters(self, filterItems: QGraphicsItem) -> None: - ... + def installSceneEventFilters(self, filterItems: QGraphicsItem) -> None: ... @overload - def installSceneEventFilters(self, filterItems: List[QGraphicsItem]) -> None: - ... + def installSceneEventFilters(self, filterItems: List[QGraphicsItem]) -> None: ... def installSceneEventFilters(self, filterItems) -> None: """ @@ -88,12 +86,10 @@ class SchedulerEvent: # PyQt5 item.installSceneEventFilter(self) @overload - def removeSceneEventFilters(self, filterItems: QGraphicsItem) -> None: - ... + def removeSceneEventFilters(self, filterItems: QGraphicsItem) -> None: ... @overload - def removeSceneEventFilters(self, filterItems: List[QGraphicsItem]) -> None: - ... + def removeSceneEventFilters(self, filterItems: List[QGraphicsItem]) -> None: ... def removeSceneEventFilters(self, filterItems) -> None: """ diff --git a/b_asic/scheduler_gui/signal_item.py b/b_asic/scheduler_gui/signal_item.py index ccb065df..723bf228 100644 --- a/b_asic/scheduler_gui/signal_item.py +++ b/b_asic/scheduler_gui/signal_item.py @@ -5,7 +5,6 @@ Contains the scheduler_gui SignalItem class for drawing and maintaining a signal in the schedule. """ - from typing import TYPE_CHECKING, cast from qtpy.QtCore import QPointF diff --git a/b_asic/sfg_generators.py b/b_asic/sfg_generators.py index 8682db1b..cdfb93b4 100644 --- a/b_asic/sfg_generators.py +++ b/b_asic/sfg_generators.py @@ -3,6 +3,7 @@ B-ASIC signal flow graph generators. This module contains a number of functions generating SFGs for specific functions. """ + from typing import Dict, Optional, Sequence, Union import numpy as np diff --git a/b_asic/signal.py b/b_asic/signal.py index df04fadc..d755bfd5 100644 --- a/b_asic/signal.py +++ b/b_asic/signal.py @@ -3,6 +3,7 @@ B-ASIC Signal Module. Contains the class for representing the connections between operations. """ + from typing import TYPE_CHECKING, Iterable, Optional, Union from b_asic.graph_component import AbstractGraphComponent, GraphComponent diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py index c3947a99..44979196 100644 --- a/b_asic/signal_flow_graph.py +++ b/b_asic/signal_flow_graph.py @@ -25,6 +25,7 @@ from typing import ( cast, ) +import numpy as np from graphviz import Digraph from b_asic.graph_component import GraphComponent @@ -1691,7 +1692,7 @@ class SFG(AbstractOperation): return Schedule(self, algorithm="ASAP").schedule_time - def dfs(self, graph, start, end): + def _dfs(self, graph, start, end): """ Find loop(s) in graph @@ -1753,16 +1754,16 @@ class SFG(AbstractOperation): visited.add(new_op) else: raise ValueError("Destination does not exist") - if dict_of_sfg == {}: + if not dict_of_sfg: raise ValueError( "the SFG does not have any loops and therefore no iteration period bound." ) cycles = [ [node] + path for node in dict_of_sfg - for path in self.dfs(dict_of_sfg, node, node) + for path in self._dfs(dict_of_sfg, node, node) ] - if cycles == []: + if not cycles: return -1 op_and_latency = {} for op in self.operations: @@ -1836,15 +1837,13 @@ class SFG(AbstractOperation): visited.add(new_op) else: raise ValueError("Destination does not exist") - if dict_of_sfg == {}: - raise ValueError( - "the SFG does not have any loops and therefore no iteration period bound." - ) + if not dict_of_sfg: + raise ValueError("Empty SFG") cycles = [ [node] + path for node in dict_of_sfg if node[0] == 't' - for path in self.dfs(dict_of_sfg, node, node) + for path in self._dfs(dict_of_sfg, node, node) ] delay_loop_list = [] for lista in cycles: @@ -1861,11 +1860,9 @@ class SFG(AbstractOperation): for x in delay_loop_list if x not in state_space_lista ] - import numpy as np mat_row = len(delay_element_used) + len(output_index_used) mat_col = len(delay_element_used) + len(input_index_used) - print(delay_element_used) mat_content = np.zeros((mat_row, mat_col)) matrix_in = [0] * mat_col matrix_answer = [0] * mat_row diff --git a/b_asic/special_operations.py b/b_asic/special_operations.py index 0e05a379..a67b55db 100644 --- a/b_asic/special_operations.py +++ b/b_asic/special_operations.py @@ -191,6 +191,7 @@ class Delay(AbstractOperation): output_count=1, name=Name(name), input_sources=[src0], + latency=0, ) self.set_param("initial_value", initial_value) diff --git a/examples/introduction.py b/examples/introduction.py index 16778e30..0caa0bd0 100644 --- a/examples/introduction.py +++ b/examples/introduction.py @@ -3,6 +3,7 @@ Introduction example for the TSTE87 course ========================================== """ + from b_asic.core_operations import Addition, ConstantMultiplication from b_asic.signal_flow_graph import SFG from b_asic.special_operations import Delay, Input, Output diff --git a/examples/schedulingexample.py b/examples/schedulingexample.py index 31663194..f68c42a6 100644 --- a/examples/schedulingexample.py +++ b/examples/schedulingexample.py @@ -14,6 +14,7 @@ Node numbering from the original SFG used with the Matlab toolbox:: sfg=addoperand(sfg,'delay',1,3,4); sfg=addoperand(sfg,'out',1,7); """ + from b_asic.signal_flow_graph import SFG from b_asic.special_operations import Delay, Input, Output diff --git a/examples/thirdorderblwdf.py b/examples/thirdorderblwdf.py index fc289e24..7496b078 100644 --- a/examples/thirdorderblwdf.py +++ b/examples/thirdorderblwdf.py @@ -5,6 +5,7 @@ Third-order Bireciprocal LWDF Small bireciprocal lattice wave digital filter. """ + import numpy as np from mplsignal.freq_plots import freqz_fir diff --git a/test/fixtures/operation_tree.py b/test/fixtures/operation_tree.py index 02134668..23c1179e 100644 --- a/test/fixtures/operation_tree.py +++ b/test/fixtures/operation_tree.py @@ -87,10 +87,10 @@ def butterfly_operation_tree(): *( Butterfly( *(Butterfly(Constant(2), Constant(4), name="bfly3").outputs), - name="bfly2" + name="bfly2", ).outputs ), - name="bfly1" + name="bfly1", ) diff --git a/test/test_gui/twotapfir.py b/test/test_gui/twotapfir.py index e0d7ca6f..44300bb8 100644 --- a/test/test_gui/twotapfir.py +++ b/test/test_gui/twotapfir.py @@ -3,15 +3,8 @@ B-ASIC automatically generated SFG file. Name: twotapfir Last saved: 2023-01-24 14:38:17.654639. """ -from b_asic import ( - SFG, - Addition, - ConstantMultiplication, - Delay, - Input, - Output, - Signal, -) + +from b_asic import SFG, Addition, ConstantMultiplication, Delay, Input, Output, Signal # Inputs: in1 = Input(name="in1") @@ -24,9 +17,7 @@ t1 = Delay(initial_value=0, name="") cmul1 = ConstantMultiplication( value=-0.5, name="cmul1", latency_offsets={'in0': None, 'out0': None} ) -add1 = Addition( - name="add1", latency_offsets={'in0': None, 'in1': None, 'out0': None} -) +add1 = Addition(name="add1", latency_offsets={'in0': None, 'in1': None, 'out0': None}) cmul2 = ConstantMultiplication( value=0.5, name="cmul2", latency_offsets={'in0': None, 'out0': None} ) diff --git a/test/test_operation.py b/test/test_operation.py index dc7ad0b1..ebcd1789 100644 --- a/test/test_operation.py +++ b/test/test_operation.py @@ -1,6 +1,7 @@ """ B-ASIC test suite for the AbstractOperation class. """ + import re import pytest diff --git a/test/test_outputport.py b/test/test_outputport.py index 3480c2eb..5ebc5655 100644 --- a/test/test_outputport.py +++ b/test/test_outputport.py @@ -1,6 +1,7 @@ """ B-ASIC test suite for OutputPort. """ + import pytest from b_asic import Signal diff --git a/test/test_sfg.py b/test/test_sfg.py index ab38f328..eae9c403 100644 --- a/test/test_sfg.py +++ b/test/test_sfg.py @@ -7,6 +7,7 @@ import sys from os import path, remove from typing import Counter, Dict, Type +import numpy as np import pytest from b_asic import Input, Output, Signal @@ -1772,3 +1773,48 @@ class TestInsertDelays: assert source2.type_name() == d_type_name assert source3.type_name() == d_type_name assert source4.type_name() == bfly.type_name() + + +class TestIterationPeriodBound: + def test_accumulator(self, sfg_simple_accumulator): + sfg_simple_accumulator.set_latency_of_type('add', 2) + assert sfg_simple_accumulator.iteration_period_bound() == 2 + + def test_no_latency(self, sfg_simple_accumulator): + with pytest.raises( + ValueError, + match="All native offsets have to set to a non-negative value to", + ): + sfg_simple_accumulator.iteration_period_bound() + + def test_secondorder_iir(self, precedence_sfg_delays): + precedence_sfg_delays.set_latency_of_type('add', 2) + precedence_sfg_delays.set_latency_of_type('cmul', 3) + assert precedence_sfg_delays.iteration_period_bound() == 10 + + def test_no_delays(self, sfg_two_inputs_two_outputs): + assert sfg_two_inputs_two_outputs.iteration_period_bound() == -1 + + +class TestStateSpace: + def test_accumulator(self, sfg_simple_accumulator): + ss = sfg_simple_accumulator.state_space_representation() + assert ss[0] == ['v0', 'y0'] + assert (ss[1] == np.array([[1.0, 1.0], [0.0, 1.0]])).all() + assert ss[2] == ['v0', 'x0'] + + def test_secondorder_iir(self, precedence_sfg_delays): + ss = precedence_sfg_delays.state_space_representation() + assert ss[0] == ['v0', 'v1', 'y0'] + + mat = np.array([[5.0, 2.0, 5.0], [1.0, 0.0, 0.0], [4.0, 6.0, 35.0]]) + assert (ss[1] == mat).all() + assert ss[2] == ['v0', 'v1', 'x0'] + + @pytest.mark.xfail() + def test_no_delays(self, sfg_two_inputs_two_outputs): + ss = sfg_two_inputs_two_outputs.state_space_representation() + + assert ss[0] == ['y0', 'y1'] + assert (ss[1] == np.array([[1.0, 1.0], [1.0, 2.0]])).all() + assert ss[2] == ['x0', 'x1'] -- GitLab