From 20506c6d9fd24c8f7973d72b073d3e7c60d8bb76 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson <oscar.gustafsson@gmail.com> Date: Sun, 16 Mar 2025 20:55:53 +0100 Subject: [PATCH] Move preference dialog to separate file --- b_asic/GUI/__init__.py | 2 +- b_asic/GUI/main_window.py | 38 +- b_asic/architecture.py | 64 +- b_asic/codegen/testbench/test.py | 4 +- b_asic/codegen/vhdl/__init__.py | 6 +- b_asic/codegen/vhdl/architecture.py | 424 ++++++------- b_asic/codegen/vhdl/common.py | 82 +-- b_asic/codegen/vhdl/entity.py | 40 +- b_asic/core_operations.py | 2 +- b_asic/gui_utils/about_window.py | 10 +- b_asic/gui_utils/color_button.py | 2 +- b_asic/gui_utils/icons.py | 66 +- b_asic/gui_utils/plot_window.py | 54 +- b_asic/logger.py | 1 + b_asic/research/interleaver.py | 4 +- b_asic/resources.py | 142 +++-- b_asic/schedule.py | 2 +- b_asic/scheduler_gui/__init__.py | 2 +- b_asic/scheduler_gui/_preferences.py | 12 +- b_asic/scheduler_gui/axes_item.py | 1 + b_asic/scheduler_gui/main_window.py | 618 +++---------------- b_asic/scheduler_gui/operation_item.py | 7 +- b_asic/scheduler_gui/preferences_dialog.py | 422 +++++++++++++ b_asic/scheduler_gui/scheduler_event.py | 1 + b_asic/scheduler_gui/scheduler_item.py | 1 + b_asic/scheduler_gui/timeline_item.py | 1 + b_asic/signal_flow_graph.py | 60 +- docs_sphinx/conf.py | 66 +- docs_sphinx/scheduler_gui.rst | 8 + test/fixtures/schedule.py | 20 +- test/fixtures/signal_flow_graph.py | 10 +- test/integration/test_sfg_to_architecture.py | 4 +- test/unit/test_architecture.py | 60 +- test/unit/test_gui.py | 30 +- test/unit/test_gui/twotapfir.py | 22 +- test/unit/test_list_schedulers.py | 4 +- test/unit/test_process.py | 6 +- test/unit/test_resources.py | 46 +- test/unit/test_schedule.py | 66 +- test/unit/test_scheduler_gui.py | 8 + test/unit/test_sfg.py | 83 ++- test/unit/test_sfg_generators.py | 32 +- test/unit/test_signal_generator.py | 6 +- test/unit/test_simulation_gui.py | 24 +- 44 files changed, 1270 insertions(+), 1293 deletions(-) create mode 100644 b_asic/scheduler_gui/preferences_dialog.py diff --git a/b_asic/GUI/__init__.py b/b_asic/GUI/__init__.py index 89256ec9..d0a06579 100644 --- a/b_asic/GUI/__init__.py +++ b/b_asic/GUI/__init__.py @@ -5,4 +5,4 @@ Graphical user interface for B-ASIC. from b_asic.GUI.main_window import start_editor -__all__ = ['start_editor'] +__all__ = ["start_editor"] diff --git a/b_asic/GUI/main_window.py b/b_asic/GUI/main_window.py index f0c05be7..1a4419c1 100644 --- a/b_asic/GUI/main_window.py +++ b/b_asic/GUI/main_window.py @@ -104,14 +104,14 @@ class SFGMainWindow(QMainWindow): # Create toolbar self._toolbar = self.addToolBar("Toolbar") - self._toolbar.addAction(get_icon('open'), "Load SFG", self.load_work) - self._toolbar.addAction(get_icon('save'), "Save SFG", self.save_work) + self._toolbar.addAction(get_icon("open"), "Load SFG", self.load_work) + self._toolbar.addAction(get_icon("save"), "Save SFG", self.save_work) self._toolbar.addSeparator() self._toolbar.addAction( - get_icon('new-sfg'), "Create SFG", self.create_sfg_from_toolbar + get_icon("new-sfg"), "Create SFG", self.create_sfg_from_toolbar ) self._toolbar.addAction( - get_icon('close'), "Clear workspace", self._clear_workspace + get_icon("close"), "Clear workspace", self._clear_workspace ) # Create status bar @@ -142,18 +142,18 @@ class SFGMainWindow(QMainWindow): self._ui.actionShowPC.triggered.connect(self._show_precedence_graph) self._ui.actionSimulateSFG.triggered.connect(self.simulate_sfg) - self._ui.actionSimulateSFG.setIcon(get_icon('sim')) + self._ui.actionSimulateSFG.setIcon(get_icon("sim")) # About menu self._ui.faqBASIC.triggered.connect(self.display_faq_page) self._ui.faqBASIC.setShortcut(QKeySequence("Ctrl+?")) - self._ui.faqBASIC.setIcon(get_icon('faq')) + self._ui.faqBASIC.setIcon(get_icon("faq")) self._ui.aboutBASIC.triggered.connect(self.display_about_page) - self._ui.aboutBASIC.setIcon(get_icon('about')) + self._ui.aboutBASIC.setIcon(get_icon("about")) self._ui.keybindsBASIC.triggered.connect(self.display_keybindings_page) - self._ui.keybindsBASIC.setIcon(get_icon('keys')) + self._ui.keybindsBASIC.setIcon(get_icon("keys")) self._ui.documentationBASIC.triggered.connect(self._open_documentation) - self._ui.documentationBASIC.setIcon(get_icon('docs')) + self._ui.documentationBASIC.setIcon(get_icon("docs")) # Operation lists self._ui.core_operations_list.itemClicked.connect( @@ -166,20 +166,20 @@ class SFGMainWindow(QMainWindow): self._on_list_widget_item_clicked ) self._ui.save_menu.triggered.connect(self.save_work) - self._ui.save_menu.setIcon(get_icon('save')) + self._ui.save_menu.setIcon(get_icon("save")) self._ui.save_menu.setShortcut(QKeySequence("Ctrl+S")) self._ui.load_menu.triggered.connect(self.load_work) - self._ui.load_menu.setIcon(get_icon('open')) + self._ui.load_menu.setIcon(get_icon("open")) self._ui.load_menu.setShortcut(QKeySequence("Ctrl+O")) self._ui.load_operations.triggered.connect(self.add_namespace) - self._ui.load_operations.setIcon(get_icon('add-operations')) + self._ui.load_operations.setIcon(get_icon("add-operations")) self._ui.exit_menu.triggered.connect(self.exit_app) - self._ui.exit_menu.setIcon(get_icon('quit')) + self._ui.exit_menu.setIcon(get_icon("quit")) self._ui.select_all.triggered.connect(self._select_all) self._ui.select_all.setShortcut(QKeySequence("Ctrl+A")) - self._ui.select_all.setIcon(get_icon('all')) + self._ui.select_all.setIcon(get_icon("all")) self._ui.unselect_all.triggered.connect(self._unselect_all) - self._ui.unselect_all.setIcon(get_icon('none')) + self._ui.unselect_all.setIcon(get_icon("none")) self._shortcut_signal = QShortcut(QKeySequence(Qt.Key_Space), self) self._shortcut_signal.activated.connect(self._connect_callback) self._create_recent_file_actions_and_menus() @@ -208,13 +208,13 @@ class SFGMainWindow(QMainWindow): # Zoom to fit self._ui.view_menu.addSeparator() - self._zoom_to_fit_action = QAction(get_icon('zoom-to-fit'), "Zoom to &fit") + self._zoom_to_fit_action = QAction(get_icon("zoom-to-fit"), "Zoom to &fit") self._zoom_to_fit_action.triggered.connect(self._zoom_to_fit) self._ui.view_menu.addAction(self._zoom_to_fit_action) # Toggle full screen self._fullscreen_action = QAction( - get_icon('full-screen'), "Toggle f&ull screen" + get_icon("full-screen"), "Toggle f&ull screen" ) self._fullscreen_action.setCheckable(True) self._fullscreen_action.triggered.connect(self._toggle_fullscreen) @@ -397,10 +397,10 @@ class SFGMainWindow(QMainWindow): """Callback for toggling full screen mode.""" if self.isFullScreen(): self.showNormal() - self._fullscreen_action.setIcon(get_icon('full-screen')) + self._fullscreen_action.setIcon(get_icon("full-screen")) else: self.showFullScreen() - self._fullscreen_action.setIcon(get_icon('full-screen-exit')) + self._fullscreen_action.setIcon(get_icon("full-screen-exit")) def _update_recent_file_list(self): settings = QSettings() diff --git a/b_asic/architecture.py b/b_asic/architecture.py index d020cf69..99625a64 100644 --- a/b_asic/architecture.py +++ b/b_asic/architecture.py @@ -68,7 +68,7 @@ class HardwareBlock: The entity name. """ if not is_valid_vhdl_identifier(entity_name): - raise ValueError(f'{entity_name} is not a valid VHDL identifier') + raise ValueError(f"{entity_name} is not a valid VHDL identifier") self._entity_name = entity_name def write_code(self, path: str) -> None: @@ -171,13 +171,13 @@ class Resource(HardwareBlock): return iter(self._collection) def _digraph(self) -> Digraph: - dg = Digraph(node_attr={'shape': 'box'}) + dg = Digraph(node_attr={"shape": "box"}) dg.node( self.entity_name, self._struct_def(), - style='filled', + style="filled", fillcolor=self._color, - fontname='Times New Roman', + fontname="Times New Roman", ) return dg @@ -199,18 +199,18 @@ class Resource(HardwareBlock): table_width = max(len(inputs), len(outputs), 1) if inputs: in_strs = [ - f'<TD COLSPAN="{int(table_width/len(inputs))}"' + f'<TD COLSPAN="{int(table_width / len(inputs))}"' f' PORT="{in_str}">{in_str}</TD>' for in_str in inputs ] ret += f"<TR>{''.join(in_strs)}</TR>" ret += ( f'<TR><TD COLSPAN="{table_width}">' - f'<B>{self.entity_name}{self._info()}</B></TD></TR>' + f"<B>{self.entity_name}{self._info()}</B></TD></TR>" ) if outputs: out_strs = [ - f'<TD COLSPAN="{int(table_width/len(outputs))}"' + f'<TD COLSPAN="{int(table_width / len(outputs))}"' f' PORT="{out_str}">{out_str}</TD>' for out_str in outputs ] @@ -274,7 +274,7 @@ class Resource(HardwareBlock): def is_assigned(self) -> bool: return self._assignment is not None - def assign(self, heuristic: str = 'left_edge'): + def assign(self, heuristic: str = "left_edge"): """ Perform assignment of processes to resource. @@ -556,7 +556,7 @@ class Memory(Resource): def _info(self) -> str: if self.is_assigned: if self._memory_type == "RAM": - plural_s = 's' if len(self._assignment) >= 2 else '' + plural_s = "s" if len(self._assignment) >= 2 else "" return f": (RAM, {len(self._assignment)} cell{plural_s})" else: pass @@ -858,7 +858,7 @@ of :class:`~b_asic.architecture.ProcessingElement` elif resource in self.processing_elements: self.processing_elements.remove(cast(ProcessingElement, resource)) else: - raise ValueError('Resource not in architecture') + raise ValueError("Resource not in architecture") def assign_resources(self, heuristic: str = "left_edge") -> None: """ @@ -951,7 +951,7 @@ of :class:`~b_asic.architecture.ProcessingElement` colored : bool, default: True Whether to color the nodes. """ - dg = Digraph(node_attr={'shape': 'box'}) + dg = Digraph(node_attr={"shape": "box"}) dg.attr(splines=splines) # Setup colors pe_color = ( @@ -988,26 +988,26 @@ of :class:`~b_asic.architecture.ProcessingElement` if cluster: # Add subgraphs if len(self._memories): - with dg.subgraph(name='cluster_memories') as c: + with dg.subgraph(name="cluster_memories") as c: for mem in self._memories: c.node( mem.entity_name, mem._struct_def(), - style='filled', + style="filled", fillcolor=memory_color, - fontname='Times New Roman', + fontname="Times New Roman", ) label = "Memory" if len(self._memories) <= 1 else "Memories" c.attr(label=label, bgcolor=memory_cluster_color) - with dg.subgraph(name='cluster_pes') as c: + with dg.subgraph(name="cluster_pes") as c: for pe in self._processing_elements: - if pe._type_name not in ('in', 'out'): + if pe._type_name not in ("in", "out"): c.node( pe.entity_name, pe._struct_def(), - style='filled', + style="filled", fillcolor=pe_color, - fontname='Times New Roman', + fontname="Times New Roman", ) label = ( "Processing element" @@ -1016,39 +1016,39 @@ of :class:`~b_asic.architecture.ProcessingElement` ) c.attr(label=label, bgcolor=pe_cluster_color) if io_cluster: - with dg.subgraph(name='cluster_io') as c: + with dg.subgraph(name="cluster_io") as c: for pe in self._processing_elements: - if pe._type_name in ('in', 'out'): + if pe._type_name in ("in", "out"): c.node( pe.entity_name, pe._struct_def(), - style='filled', + style="filled", fillcolor=io_color, - fontname='Times New Roman', + fontname="Times New Roman", ) c.attr(label="IO", bgcolor=io_cluster_color) else: for pe in self._processing_elements: - if pe._type_name in ('in', 'out'): + if pe._type_name in ("in", "out"): dg.node( pe.entity_name, pe._struct_def(), - style='filled', + style="filled", fillcolor=io_color, - fontname='Times New Roman', + fontname="Times New Roman", ) else: for mem in self._memories: dg.node( mem.entity_name, mem._struct_def(), - style='filled', + style="filled", fillcolor=memory_color, - fontname='Times New Roman', + fontname="Times New Roman", ) for pe in self._processing_elements: dg.node( - pe.entity_name, pe._struct_def(), style='filled', fillcolor=pe_color + pe.entity_name, pe._struct_def(), style="filled", fillcolor=pe_color ) # Create list of interconnects @@ -1103,9 +1103,9 @@ of :class:`~b_asic.architecture.ProcessingElement` dg.node( name, ret + "</TABLE>>", - style='filled', + style="filled", fillcolor=mux_color, - fontname='Times New Roman', + fontname="Times New Roman", ) # Add edge from mux output to resource input dg.edge(f"{name}:out0", destination_str) @@ -1115,8 +1115,8 @@ of :class:`~b_asic.architecture.ProcessingElement` original_src_str = src_str if len(destination_counts) > 1 and branch_node: branch = f"{src_str}_branch".replace(":", "") - dg.node(branch, shape='point') - dg.edge(src_str, branch, arrowhead='none') + dg.node(branch, shape="point") + dg.edge(src_str, branch, arrowhead="none") src_str = branch for destination_str, cnt_str in destination_counts: if multiplexers and len(destination_list[destination_str]) > 1: diff --git a/b_asic/codegen/testbench/test.py b/b_asic/codegen/testbench/test.py index d0f45588..b7471e96 100755 --- a/b_asic/codegen/testbench/test.py +++ b/b_asic/codegen/testbench/test.py @@ -8,12 +8,12 @@ from vunit import VUnit # Absolute path of the testbench directory testbench_path = dirname(abspath(__file__)) -vu = VUnit.from_argv(argv=['--output-path', f'{testbench_path}/vunit_out'] + argv[1:]) +vu = VUnit.from_argv(argv=["--output-path", f"{testbench_path}/vunit_out"] + argv[1:]) lib = vu.add_library("lib") lib.add_source_files( [ - f'{testbench_path}/*.vhdl', + f"{testbench_path}/*.vhdl", ] ) lib.set_compile_option("modelsim.vcom_flags", ["-2008"]) diff --git a/b_asic/codegen/vhdl/__init__.py b/b_asic/codegen/vhdl/__init__.py index 2cce156a..20345075 100644 --- a/b_asic/codegen/vhdl/__init__.py +++ b/b_asic/codegen/vhdl/__init__.py @@ -13,7 +13,7 @@ def write( indent_level: int, text: str, *, - end: str = '\n', + end: str = "\n", start: Optional[str] = None, ): """ @@ -39,7 +39,7 @@ def write( """ if start is not None: f.write(start) - f.write(f'{VHDL_TAB*indent_level}{text}{end}') + f.write(f"{VHDL_TAB * indent_level}{text}{end}") def write_lines(f: TextIO, lines: List[Union[Tuple[int, str], Tuple[int, str, str]]]): @@ -65,4 +65,4 @@ def write_lines(f: TextIO, lines: List[Union[Tuple[int, str], Tuple[int, str, st elif len(tpl) == 3: write(f, indent_level=tpl[0], text=str(tpl[1]), end=str(tpl[2])) else: - raise ValueError('All tuples in list `lines` must have length 2 or 3') + raise ValueError("All tuples in list `lines` must have length 2 or 3") diff --git a/b_asic/codegen/vhdl/architecture.py b/b_asic/codegen/vhdl/architecture.py index 439ae4ba..524f8c9b 100644 --- a/b_asic/codegen/vhdl/architecture.py +++ b/b_asic/codegen/vhdl/architecture.py @@ -77,94 +77,94 @@ def memory_based_storage( ) # Next power-of-two # Write architecture header - write(f, 0, f'architecture {architecture_name} of {entity_name} is', end='\n\n') + write(f, 0, f"architecture {architecture_name} of {entity_name} is", end="\n\n") # # Architecture declarative region begin # - write(f, 1, '-- HDL memory description') + write(f, 1, "-- HDL memory description") common.constant_declaration( - f, name='MEM_WL', signal_type='integer', value=word_length, name_pad=16 + f, name="MEM_WL", signal_type="integer", value=word_length, name_pad=16 ) common.constant_declaration( - f, name='MEM_DEPTH', signal_type='integer', value=mem_depth, name_pad=16 + f, name="MEM_DEPTH", signal_type="integer", value=mem_depth, name_pad=16 ) common.type_declaration( - f, 'mem_type', 'array(0 to MEM_DEPTH-1) of std_logic_vector(MEM_WL-1 downto 0)' + f, "mem_type", "array(0 to MEM_DEPTH-1) of std_logic_vector(MEM_WL-1 downto 0)" ) common.signal_declaration( f, - name='memory', - signal_type='mem_type', + name="memory", + signal_type="mem_type", name_pad=18, - vivado_ram_style='distributed', # Xilinx Vivado distributed RAM + vivado_ram_style="distributed", # Xilinx Vivado distributed RAM ) # Schedule time counter - write(f, 1, '-- Schedule counter', start='\n') + write(f, 1, "-- Schedule counter", start="\n") common.constant_declaration( f, - name='SCHEDULE_CNT_LEN', - signal_type='integer', + name="SCHEDULE_CNT_LEN", + signal_type="integer", value=ceil(log2(schedule_time)), name_pad=16, ) common.signal_declaration( f, - name='schedule_cnt', - signal_type='unsigned(SCHEDULE_CNT_LEN-1 downto 0)', + name="schedule_cnt", + signal_type="unsigned(SCHEDULE_CNT_LEN-1 downto 0)", name_pad=18, ) for i in range(adr_pipe_depth): common.signal_declaration( f, - name=f'schedule_cnt{i+1}', - signal_type='unsigned(SCHEDULE_CNT_LEN-1 downto 0)', + name=f"schedule_cnt{i + 1}", + signal_type="unsigned(SCHEDULE_CNT_LEN-1 downto 0)", name_pad=18, ) common.constant_declaration( f, - name='ADR_LEN', - signal_type='integer', - value=f'SCHEDULE_CNT_LEN-({int(log2(adr_mux_size))}*{adr_pipe_depth})', + name="ADR_LEN", + signal_type="integer", + value=f"SCHEDULE_CNT_LEN-({int(log2(adr_mux_size))}*{adr_pipe_depth})", name_pad=16, ) common.alias_declaration( f, - name='schedule_cnt_adr', - signal_type='unsigned(ADR_LEN-1 downto 0)', - value='schedule_cnt(ADR_LEN-1 downto 0)', + name="schedule_cnt_adr", + signal_type="unsigned(ADR_LEN-1 downto 0)", + value="schedule_cnt(ADR_LEN-1 downto 0)", name_pad=19, ) # Address generation signals - write(f, 1, '-- Memory address generation', start='\n') + write(f, 1, "-- Memory address generation", start="\n") for i in range(read_ports): common.signal_declaration( - f, f'read_port_{i}', 'std_logic_vector(MEM_WL-1 downto 0)', name_pad=18 + f, f"read_port_{i}", "std_logic_vector(MEM_WL-1 downto 0)", name_pad=18 ) common.signal_declaration( - f, f'read_adr_{i}', 'integer range 0 to MEM_DEPTH-1', name_pad=18 + f, f"read_adr_{i}", "integer range 0 to MEM_DEPTH-1", name_pad=18 ) - common.signal_declaration(f, f'read_en_{i}', 'std_logic', name_pad=18) + common.signal_declaration(f, f"read_en_{i}", "std_logic", name_pad=18) for i in range(write_ports): common.signal_declaration( - f, f'write_port_{i}', 'std_logic_vector(MEM_WL-1 downto 0)', name_pad=18 + f, f"write_port_{i}", "std_logic_vector(MEM_WL-1 downto 0)", name_pad=18 ) common.signal_declaration( - f, f'write_adr_{i}', 'integer range 0 to MEM_DEPTH-1', name_pad=18 + f, f"write_adr_{i}", "integer range 0 to MEM_DEPTH-1", name_pad=18 ) - common.signal_declaration(f, f'write_en_{i}', 'std_logic', name_pad=18) + common.signal_declaration(f, f"write_en_{i}", "std_logic", name_pad=18) # Address generation mutltiplexing signals - write(f, 1, '-- Address generation multiplexing signals', start='\n') + write(f, 1, "-- Address generation multiplexing signals", start="\n") for write_port_idx in range(write_ports): for depth in range(adr_pipe_depth + 1): for rom in range(total_roms // adr_mux_size**depth): common.signal_declaration( f, - f'write_adr_{write_port_idx}_{depth}_{rom}', - signal_type='integer range 0 to MEM_DEPTH-1', + f"write_adr_{write_port_idx}_{depth}_{rom}", + signal_type="integer range 0 to MEM_DEPTH-1", name_pad=18, ) for write_port_idx in range(write_ports): @@ -172,8 +172,8 @@ def memory_based_storage( for rom in range(total_roms // adr_mux_size**depth): common.signal_declaration( f, - f'write_en_{write_port_idx}_{depth}_{rom}', - signal_type='std_logic', + f"write_en_{write_port_idx}_{depth}_{rom}", + signal_type="std_logic", name_pad=18, ) for read_port_idx in range(read_ports): @@ -181,17 +181,17 @@ def memory_based_storage( for rom in range(total_roms // adr_mux_size**depth): common.signal_declaration( f, - f'read_adr_{read_port_idx}_{depth}_{rom}', - signal_type='integer range 0 to MEM_DEPTH-1', + f"read_adr_{read_port_idx}_{depth}_{rom}", + signal_type="integer range 0 to MEM_DEPTH-1", name_pad=18, ) # Input sync signals if input_sync: - write(f, 1, '-- Input synchronization', start='\n') + write(f, 1, "-- Input synchronization", start="\n") for i in range(read_ports): common.signal_declaration( - f, f'p_{i}_in_sync', 'std_logic_vector(WL-1 downto 0)', name_pad=18 + f, f"p_{i}_in_sync", "std_logic_vector(WL-1 downto 0)", name_pad=18 ) # @@ -199,86 +199,86 @@ def memory_based_storage( # # Schedule counter - write(f, 0, 'begin', start='\n', end='\n\n') - write(f, 1, '-- Schedule counter') - common.synchronous_process_prologue(f=f, name='schedule_cnt_proc', clk='clk') + write(f, 0, "begin", start="\n", end="\n\n") + write(f, 1, "-- Schedule counter") + common.synchronous_process_prologue(f=f, name="schedule_cnt_proc", clk="clk") write_lines( f, [ - (3, 'if rst = \'1\' then'), + (3, "if rst = '1' then"), (4, "schedule_cnt <= (others => '0');"), - (3, 'else'), - (4, 'if en = \'1\' then'), - (5, f'if schedule_cnt = {schedule_time-1} then'), + (3, "else"), + (4, "if en = '1' then"), + (5, f"if schedule_cnt = {schedule_time - 1} then"), (6, "schedule_cnt <= (others => '0');"), - (5, 'else'), - (6, 'schedule_cnt <= schedule_cnt + 1;'), - (5, 'end if;'), - (4, 'end if;'), + (5, "else"), + (6, "schedule_cnt <= schedule_cnt + 1;"), + (5, "end if;"), + (4, "end if;"), ], ) for i in range(adr_pipe_depth): if i == 0: - write(f, 4, 'schedule_cnt1 <= schedule_cnt;') + write(f, 4, "schedule_cnt1 <= schedule_cnt;") else: - write(f, 4, f'schedule_cnt{i+1} <= schedule_cnt{i};') - write(f, 3, 'end if;') + write(f, 4, f"schedule_cnt{i + 1} <= schedule_cnt{i};") + write(f, 3, "end if;") common.synchronous_process_epilogue( f=f, - name='schedule_cnt_proc', - clk='clk', + name="schedule_cnt_proc", + clk="clk", ) # Input synchronization if input_sync: - write(f, 1, '-- Input synchronization', start='\n') + write(f, 1, "-- Input synchronization", start="\n") common.synchronous_process_prologue( f=f, - name='input_sync_proc', - clk='clk', + name="input_sync_proc", + clk="clk", ) for i in range(read_ports): - write(f, 3, f'p_{i}_in_sync <= p_{i}_in;') + write(f, 3, f"p_{i}_in_sync <= p_{i}_in;") common.synchronous_process_epilogue( f=f, - name='input_sync_proc', - clk='clk', + name="input_sync_proc", + clk="clk", ) # Infer the memory - write(f, 1, '-- Memory', start='\n') + write(f, 1, "-- Memory", start="\n") common.asynchronous_read_memory( f=f, - clk='clk', - name=f'mem_{0}_proc', + clk="clk", + name=f"mem_{0}_proc", read_ports={ - (f'read_port_{i}', f'read_adr_{i}', f'read_en_{i}') + (f"read_port_{i}", f"read_adr_{i}", f"read_en_{i}") for i in range(read_ports) }, write_ports={ - (f'write_port_{i}', f'write_adr_{i}', f'write_en_{i}') + (f"write_port_{i}", f"write_adr_{i}", f"write_en_{i}") for i in range(write_ports) }, ) - write(f, 1, f'read_adr_0 <= read_adr_0_{adr_pipe_depth}_0;') - write(f, 1, f'write_adr_0 <= write_adr_0_{adr_pipe_depth}_0;') - write(f, 1, f'write_en_0 <= write_en_0_{adr_pipe_depth}_0;') + write(f, 1, f"read_adr_0 <= read_adr_0_{adr_pipe_depth}_0;") + write(f, 1, f"write_adr_0 <= write_adr_0_{adr_pipe_depth}_0;") + write(f, 1, f"write_en_0 <= write_en_0_{adr_pipe_depth}_0;") if input_sync: - write(f, 1, 'write_port_0 <= p_0_in_sync;') + write(f, 1, "write_port_0 <= p_0_in_sync;") else: - write(f, 1, 'write_port_0 <= p_0_in;') + write(f, 1, "write_port_0 <= p_0_in;") # Input and output assignments - write(f, 1, '-- Input and output assignments', start='\n') + write(f, 1, "-- Input and output assignments", start="\n") p_zero_exec = filter( lambda p: p.execution_time == 0, (p for pc in assignment for p in pc) ) common.synchronous_process_prologue( f, - clk='clk', - name='output_reg_proc', + clk="clk", + name="output_reg_proc", ) - write(f, 3, 'case to_integer(schedule_cnt) is') + write(f, 3, "case to_integer(schedule_cnt) is") for p in p_zero_exec: if input_sync: write_time = (p.start_time + 1) % schedule_time @@ -286,32 +286,32 @@ def memory_based_storage( write( f, 4, - f'when {write_time}+{adr_pipe_depth} => p_0_out <= p_0_in_sync;', + f"when {write_time}+{adr_pipe_depth} => p_0_out <= p_0_in_sync;", ) else: - write(f, 4, f'when {write_time} => p_0_out <= p_0_in_sync;') + write(f, 4, f"when {write_time} => p_0_out <= p_0_in_sync;") else: write_time = (p.start_time) % schedule_time - write(f, 4, f'when {write_time} => p_0_out <= p_0_in;') + write(f, 4, f"when {write_time} => p_0_out <= p_0_in;") write_lines( f, [ - (4, 'when others => p_0_out <= read_port_0;'), - (3, 'end case;'), + (4, "when others => p_0_out <= read_port_0;"), + (3, "end case;"), ], ) common.synchronous_process_epilogue( f, - clk='clk', - name='output_reg_proc', + clk="clk", + name="output_reg_proc", ) # # ROM Write address generation # - write(f, 1, '--', start='\n') - write(f, 1, '-- Memory write address generation', start='') - write(f, 1, '--', end='\n') + write(f, 1, "--", start="\n") + write(f, 1, "-- Memory write address generation", start="") + write(f, 1, "--", end="\n") # Extract all the write addresses write_list: List[Optional[Tuple[int, MemoryVariable]]] = [ @@ -321,7 +321,7 @@ def memory_based_storage( for mv in collection: mv = cast(MemoryVariable, mv) if mv.start_time >= schedule_time: - raise ValueError('start_time greater than schedule_time') + raise ValueError("start_time greater than schedule_time") if mv.execution_time: write_list[mv.start_time] = (i, mv) @@ -334,32 +334,32 @@ def memory_based_storage( common.process_prologue( f, sensitivity_list="schedule_cnt_adr", name="mem_write_address_proc" ) - write(f, 3, 'case to_integer(schedule_cnt_adr) is') + write(f, 3, "case to_integer(schedule_cnt_adr) is") list_start_idx = rom * elements_per_rom list_stop_idx = list_start_idx + elements_per_rom for i, mv in filter(None, write_list[list_start_idx:list_stop_idx]): write_lines( f, [ - (4, f'-- {mv!r}'), + (4, f"-- {mv!r}"), ( 4, ( - f'when {mv.start_time % schedule_time} mod' - f' {elements_per_rom} =>' + f"when {mv.start_time % schedule_time} mod" + f" {elements_per_rom} =>" ), ), - (5, f'write_adr_0_{0}_{rom} <= {i};'), - (5, f'write_en_0_{0}_{rom} <= \'1\';'), + (5, f"write_adr_0_{0}_{rom} <= {i};"), + (5, f"write_en_0_{0}_{rom} <= '1';"), ], ) write_lines( f, [ - (4, 'when others =>'), - (5, f'write_adr_0_{0}_{rom} <= 0;'), - (5, f'write_en_0_{0}_{rom} <= \'0\';'), - (3, 'end case;'), + (4, "when others =>"), + (5, f"write_adr_0_{0}_{rom} <= 0;"), + (5, f"write_en_0_{0}_{rom} <= '0';"), + (3, "end case;"), ], ) if input_sync: @@ -377,16 +377,16 @@ def memory_based_storage( for layer in range(adr_pipe_depth): for mux_idx in range(total_roms // adr_mux_size ** (layer + 1)): common.synchronous_process_prologue( - f, clk='clk', name=f'mem_write_address_proc{layer+1}_{mux_idx}' + f, clk="clk", name=f"mem_write_address_proc{layer + 1}_{mux_idx}" ) write( f, 3, ( - f'case to_integer(schedule_cnt{layer+1}(' - f'ADR_LEN+{layer*bits_per_mux + bits_per_mux - 1} downto ' - f'ADR_LEN+{layer*bits_per_mux}' - ')) is' + f"case to_integer(schedule_cnt{layer + 1}(" + f"ADR_LEN+{layer * bits_per_mux + bits_per_mux - 1} downto " + f"ADR_LEN+{layer * bits_per_mux}" + ")) is" ), ) for in_idx in range(adr_mux_size): @@ -395,26 +395,26 @@ def memory_based_storage( f, 4, ( - f'-- {adr_mux_size}-to-1 MUX layer: ' - f'layer={layer}, MUX={mux_idx}, input={in_idx}' + f"-- {adr_mux_size}-to-1 MUX layer: " + f"layer={layer}, MUX={mux_idx}, input={in_idx}" ), ) write_lines( f, [ - (4, f'when {in_idx} =>'), + (4, f"when {in_idx} =>"), ( 5, ( - f'write_adr_0_{layer+1}_{mux_idx} <=' - f' write_adr_0_{layer}_{out_idx};' + f"write_adr_0_{layer + 1}_{mux_idx} <=" + f" write_adr_0_{layer}_{out_idx};" ), ), ( 5, ( - f'write_en_0_{layer+1}_{mux_idx} <=' - f' write_en_0_{layer}_{out_idx};' + f"write_en_0_{layer + 1}_{mux_idx} <=" + f" write_en_0_{layer}_{out_idx};" ), ), ], @@ -422,23 +422,23 @@ def memory_based_storage( write_lines( f, [ - (4, 'when others =>'), - (5, f'write_adr_0_{layer+1}_{mux_idx} <= 0;'), - (5, f'write_en_0_{layer+1}_{mux_idx} <= \'0\';'), - (3, 'end case;'), + (4, "when others =>"), + (5, f"write_adr_0_{layer + 1}_{mux_idx} <= 0;"), + (5, f"write_en_0_{layer + 1}_{mux_idx} <= '0';"), + (3, "end case;"), ], ) common.synchronous_process_epilogue( - f, clk='clk', name=f'mem_write_address_proc{layer+1}_{mux_idx}' + f, clk="clk", name=f"mem_write_address_proc{layer + 1}_{mux_idx}" ) write(f, 1, "") # # ROM read address generation # - write(f, 1, '--', start='\n') - write(f, 1, '-- Memory read address generation', start='') - write(f, 1, '--', end='\n') + write(f, 1, "--", start="\n") + write(f, 1, "-- Memory read address generation", start="") + write(f, 1, "--", end="\n") # Extract all the read addresses read_list: List[Optional[Tuple[int, MemoryVariable]]] = [ @@ -461,7 +461,7 @@ def memory_based_storage( common.process_prologue( f, sensitivity_list="schedule_cnt_adr", name="mem_read_address_proc" ) - write(f, 3, 'case to_integer(schedule_cnt_adr) is') + write(f, 3, "case to_integer(schedule_cnt_adr) is") list_start_idx = rom * elements_per_rom list_stop_idx = list_start_idx + elements_per_rom for idx in range(list_start_idx, list_stop_idx): @@ -474,17 +474,17 @@ def memory_based_storage( write_lines( f, [ - (4, f'-- {mv!r}'), - (4, f'when {idx} mod {elements_per_rom} =>'), - (5, f'read_adr_0_{0}_{rom} <= {i};'), + (4, f"-- {mv!r}"), + (4, f"when {idx} mod {elements_per_rom} =>"), + (5, f"read_adr_0_{0}_{rom} <= {i};"), ], ) write_lines( f, [ - (4, 'when others =>'), - (5, f'read_adr_0_{0}_{rom} <= 0;'), - (3, 'end case;'), + (4, "when others =>"), + (5, f"read_adr_0_{0}_{rom} <= 0;"), + (3, "end case;"), ], ) if input_sync: @@ -502,16 +502,16 @@ def memory_based_storage( for layer in range(adr_pipe_depth): for mux_idx in range(total_roms // adr_mux_size ** (layer + 1)): common.synchronous_process_prologue( - f, clk='clk', name=f'mem_read_address_proc{layer+1}_{mux_idx}' + f, clk="clk", name=f"mem_read_address_proc{layer + 1}_{mux_idx}" ) write( f, 3, ( - f'case to_integer(schedule_cnt{layer+1}(' - f'ADR_LEN+{layer*bits_per_mux + bits_per_mux - 1} downto ' - f'ADR_LEN+{layer*bits_per_mux}' - ')) is' + f"case to_integer(schedule_cnt{layer + 1}(" + f"ADR_LEN+{layer * bits_per_mux + bits_per_mux - 1} downto " + f"ADR_LEN+{layer * bits_per_mux}" + ")) is" ), ) for in_idx in range(adr_mux_size): @@ -520,19 +520,19 @@ def memory_based_storage( f, 4, ( - f'-- {adr_mux_size}-to-1 MUX layer: ' - f'layer={layer}, MUX={mux_idx}, input={in_idx}' + f"-- {adr_mux_size}-to-1 MUX layer: " + f"layer={layer}, MUX={mux_idx}, input={in_idx}" ), ) write_lines( f, [ - (4, f'when {in_idx} =>'), + (4, f"when {in_idx} =>"), ( 5, ( - f'read_adr_0_{layer+1}_{mux_idx} <=' - f' read_adr_0_{layer}_{out_idx};' + f"read_adr_0_{layer + 1}_{mux_idx} <=" + f" read_adr_0_{layer}_{out_idx};" ), ), ], @@ -540,17 +540,17 @@ def memory_based_storage( write_lines( f, [ - (4, 'when others =>'), - (5, f'read_adr_0_{layer+1}_{mux_idx} <= 0;'), - (3, 'end case;'), + (4, "when others =>"), + (5, f"read_adr_0_{layer + 1}_{mux_idx} <= 0;"), + (3, "end case;"), ], ) common.synchronous_process_epilogue( - f, clk='clk', name=f'mem_read_address_proc{layer+1}_{mux_idx}' + f, clk="clk", name=f"mem_read_address_proc{layer + 1}_{mux_idx}" ) write(f, 1, "") - write(f, 0, f'end architecture {architecture_name};', start='\n') + write(f, 0, f"end architecture {architecture_name};", start="\n") def register_based_storage( @@ -594,86 +594,86 @@ def register_based_storage( # Architecture declarative region begin # # Write architecture header - write(f, 0, f'architecture {architecture_name} of {entity_name} is', end='\n\n') + write(f, 0, f"architecture {architecture_name} of {entity_name} is", end="\n\n") # Schedule time counter - write(f, 1, '-- Schedule counter') + write(f, 1, "-- Schedule counter") common.signal_declaration( f, - name='schedule_cnt', - signal_type=f'integer range 0 to {schedule_time}-1', + name="schedule_cnt", + signal_type=f"integer range 0 to {schedule_time}-1", name_pad=18, - default_value='0', + default_value="0", ) # Shift register - write(f, 1, '-- Shift register', start='\n') + write(f, 1, "-- Shift register", start="\n") common.type_declaration( f, - name='shift_reg_type', - alias=f'array(0 to {reg_cnt}-1) of std_logic_vector(WL-1 downto 0)', + name="shift_reg_type", + alias=f"array(0 to {reg_cnt}-1) of std_logic_vector(WL-1 downto 0)", ) common.signal_declaration( f, - name='shift_reg', - signal_type='shift_reg_type', + name="shift_reg", + signal_type="shift_reg_type", name_pad=18, ) # Back edge mux decoder - write(f, 1, '-- Back-edge mux select signal', start='\n') + write(f, 1, "-- Back-edge mux select signal", start="\n") common.signal_declaration( f, - name='back_edge_mux_sel', - signal_type=f'integer range 0 to {len(back_edges)}', + name="back_edge_mux_sel", + signal_type=f"integer range 0 to {len(back_edges)}", name_pad=18, ) # Output mux selector - write(f, 1, '-- Output mux select signal', start='\n') + write(f, 1, "-- Output mux select signal", start="\n") common.signal_declaration( f, - name='out_mux_sel', - signal_type=f'integer range 0 to {len(output_regs) - 1}', + name="out_mux_sel", + signal_type=f"integer range 0 to {len(output_regs) - 1}", name_pad=18, ) # # Architecture body begin # - write(f, 0, 'begin', start='\n', end='\n\n') - write(f, 1, '-- Schedule counter') + write(f, 0, "begin", start="\n", end="\n\n") + write(f, 1, "-- Schedule counter") common.synchronous_process_prologue( f=f, - name='schedule_cnt_proc', - clk='clk', + name="schedule_cnt_proc", + clk="clk", ) write_lines( f, [ - (4, 'if en = \'1\' then'), - (5, f'if schedule_cnt = {schedule_time}-1 then'), - (6, 'schedule_cnt <= 0;'), - (5, 'else'), - (6, 'schedule_cnt <= schedule_cnt + 1;'), - (5, 'end if;'), - (4, 'end if;'), + (4, "if en = '1' then"), + (5, f"if schedule_cnt = {schedule_time}-1 then"), + (6, "schedule_cnt <= 0;"), + (5, "else"), + (6, "schedule_cnt <= schedule_cnt + 1;"), + (5, "end if;"), + (4, "end if;"), ], ) common.synchronous_process_epilogue( f=f, - name='schedule_cnt_proc', - clk='clk', + name="schedule_cnt_proc", + clk="clk", ) # Shift register back-edge decoding - write(f, 1, '-- Shift register back-edge decoding', start='\n') + write(f, 1, "-- Shift register back-edge decoding", start="\n") common.synchronous_process_prologue( f, - clk='clk', - name='shift_reg_back_edge_decode_proc', + clk="clk", + name="shift_reg_back_edge_decode_proc", ) - write(f, 3, 'case schedule_cnt is') + write(f, 3, "case schedule_cnt is") for time, entry in enumerate(forward_backward_table): if entry.back_edge_to: assert len(entry.back_edge_to) == 1 @@ -682,104 +682,104 @@ def register_based_storage( write_lines( f, [ - (4, f'when {(time-1)%schedule_time} =>'), - (5, f'-- ({src} -> {dst})'), - (5, f'back_edge_mux_sel <= {mux_idx};'), + (4, f"when {(time - 1) % schedule_time} =>"), + (5, f"-- ({src} -> {dst})"), + (5, f"back_edge_mux_sel <= {mux_idx};"), ], ) write_lines( f, [ - (4, 'when others =>'), - (5, 'back_edge_mux_sel <= 0;'), - (3, 'end case;'), + (4, "when others =>"), + (5, "back_edge_mux_sel <= 0;"), + (3, "end case;"), ], ) common.synchronous_process_epilogue( f, - clk='clk', - name='shift_reg_back_edge_decode_proc', + clk="clk", + name="shift_reg_back_edge_decode_proc", ) # Shift register multiplexer logic - write(f, 1, '-- Multiplexers for shift register', start='\n') + write(f, 1, "-- Multiplexers for shift register", start="\n") common.synchronous_process_prologue( f, - clk='clk', - name='shift_reg_proc', + clk="clk", + name="shift_reg_proc", ) if sync_rst: - write(f, 3, 'if rst = \'1\' then') + write(f, 3, "if rst = '1' then") for reg_idx in range(reg_cnt): - write(f, 4, f'shift_reg({reg_idx}) <= (others => \'0\');') - write(f, 3, 'else') + write(f, 4, f"shift_reg({reg_idx}) <= (others => '0');") + write(f, 3, "else") write_lines( f, [ - (3, '-- Default case'), - (3, 'shift_reg(0) <= p_0_in;'), + (3, "-- Default case"), + (3, "shift_reg(0) <= p_0_in;"), ], ) for reg_idx in range(1, reg_cnt): - write(f, 3, f'shift_reg({reg_idx}) <= shift_reg({reg_idx-1});') - write(f, 3, 'case back_edge_mux_sel is') + write(f, 3, f"shift_reg({reg_idx}) <= shift_reg({reg_idx - 1});") + write(f, 3, "case back_edge_mux_sel is") for edge, mux_sel in back_edge_table.items(): write_lines( f, [ - (4, f'when {mux_sel} =>'), - (5, f'shift_reg({edge[1]}) <= shift_reg({edge[0]});'), + (4, f"when {mux_sel} =>"), + (5, f"shift_reg({edge[1]}) <= shift_reg({edge[0]});"), ], ) write_lines( f, [ - (4, 'when others => null;'), - (3, 'end case;'), + (4, "when others => null;"), + (3, "end case;"), ], ) if sync_rst: - write(f, 3, 'end if;') + write(f, 3, "end if;") common.synchronous_process_epilogue( f, - clk='clk', - name='shift_reg_proc', + clk="clk", + name="shift_reg_proc", ) # Output multiplexer decoding logic - write(f, 1, '-- Output multiplexer decoding logic', start='\n') - common.synchronous_process_prologue(f, clk='clk', name='out_mux_decode_proc') - write(f, 3, 'case schedule_cnt is') + write(f, 1, "-- Output multiplexer decoding logic", start="\n") + common.synchronous_process_prologue(f, clk="clk", name="out_mux_decode_proc") + write(f, 3, "case schedule_cnt is") for i, entry in enumerate(forward_backward_table): if entry.outputs_from is not None: sel = output_mux_table[entry.outputs_from] - write(f, 4, f'when {(i-1)%schedule_time} =>') - write(f, 5, f'out_mux_sel <= {sel};') - write(f, 3, 'end case;') - common.synchronous_process_epilogue(f, clk='clk', name='out_mux_decode_proc') + write(f, 4, f"when {(i - 1) % schedule_time} =>") + write(f, 5, f"out_mux_sel <= {sel};") + write(f, 3, "end case;") + common.synchronous_process_epilogue(f, clk="clk", name="out_mux_decode_proc") # Output multiplexer logic - write(f, 1, '-- Output multiplexer', start='\n') + write(f, 1, "-- Output multiplexer", start="\n") common.synchronous_process_prologue( f, - clk='clk', - name='out_mux_proc', + clk="clk", + name="out_mux_proc", ) - write(f, 3, 'case out_mux_sel is') + write(f, 3, "case out_mux_sel is") for reg_i, mux_i in output_mux_table.items(): - write(f, 4, f'when {mux_i} =>') + write(f, 4, f"when {mux_i} =>") if reg_i < 0: - write(f, 5, f'p_0_out <= p_{-1-reg_i}_in;') + write(f, 5, f"p_0_out <= p_{-1 - reg_i}_in;") else: - write(f, 5, f'p_0_out <= shift_reg({reg_i});') - write(f, 3, 'end case;') + write(f, 5, f"p_0_out <= shift_reg({reg_i});") + write(f, 3, "end case;") common.synchronous_process_epilogue( f, - clk='clk', - name='out_mux_proc', + clk="clk", + name="out_mux_proc", ) - write(f, 0, f'end architecture {architecture_name};', start='\n') + write(f, 0, f"end architecture {architecture_name};", start="\n") diff --git a/b_asic/codegen/vhdl/common.py b/b_asic/codegen/vhdl/common.py index 7baeedf3..198eb2d6 100644 --- a/b_asic/codegen/vhdl/common.py +++ b/b_asic/codegen/vhdl/common.py @@ -22,25 +22,25 @@ def b_asic_preamble(f: TextIO): # Try to acquire the current git commit hash git_commit_id = None try: - process = Popen(['git', 'rev-parse', '--short', 'HEAD'], stdout=PIPE) - git_commit_id = process.communicate()[0].decode('utf-8').strip() + process = Popen(["git", "rev-parse", "--short", "HEAD"], stdout=PIPE) + git_commit_id = process.communicate()[0].decode("utf-8").strip() except: # noqa: E722 pass write_lines( f, [ - (0, '--'), - (0, '-- This code was automatically generated by the B-ASIC toolbox.'), - (0, f'-- Code generation timestamp: ({datetime.now()})'), + (0, "--"), + (0, "-- This code was automatically generated by the B-ASIC toolbox."), + (0, f"-- Code generation timestamp: ({datetime.now()})"), ], ) if git_commit_id: - write(f, 0, f'-- B-ASIC short commit hash: {git_commit_id}') + write(f, 0, f"-- B-ASIC short commit hash: {git_commit_id}") write_lines( f, [ - (0, '-- URL: https://gitlab.liu.se/da/B-ASIC'), - (0, '--', '\n\n'), + (0, "-- URL: https://gitlab.liu.se/da/B-ASIC"), + (0, "--", "\n\n"), ], ) @@ -64,12 +64,12 @@ def ieee_header( numeric_std : bool, default: True Include the numeric_std header. """ - write(f, 0, 'library ieee;') + write(f, 0, "library ieee;") if std_logic_1164: - write(f, 0, 'use ieee.std_logic_1164.all;') + write(f, 0, "use ieee.std_logic_1164.all;") if numeric_std: - write(f, 0, 'use ieee.numeric_std.all;') - write(f, 0, '') + write(f, 0, "use ieee.numeric_std.all;") + write(f, 0, "") def signal_declaration( @@ -111,15 +111,15 @@ def signal_declaration( """ # Spacing of VHDL signals declaration always with a single tab name_pad = name_pad or 0 - write(f, 1, f'signal {name:<{name_pad}} : {signal_type}', end='') + write(f, 1, f"signal {name:<{name_pad}} : {signal_type}", end="") if default_value is not None: - write(f, 0, f' := {default_value}', end='') - write(f, 0, ';') + write(f, 0, f" := {default_value}", end="") + write(f, 0, ";") if vivado_ram_style is not None: write_lines( f, [ - (1, 'attribute ram_style : string;'), + (1, "attribute ram_style : string;"), (1, f'attribute ram_style of {name} : signal is "{vivado_ram_style}";'), ], ) @@ -127,7 +127,7 @@ def signal_declaration( write_lines( f, [ - (1, 'attribute ramstyle : string;'), + (1, "attribute ramstyle : string;"), (1, f'attribute ramstyle of {name} : signal is "{quartus_ram_style}";'), ], ) @@ -141,7 +141,7 @@ def alias_declaration( name_pad: Optional[int] = None, ): name_pad = name_pad or 0 - write(f, 1, f'alias {name:<{name_pad}} : {signal_type} is {value};') + write(f, 1, f"alias {name:<{name_pad}} : {signal_type} is {value};") def constant_declaration( @@ -168,7 +168,7 @@ def constant_declaration( An optional left padding value applied to the name. """ name_pad = 0 if name_pad is None else name_pad - write(f, 1, f'constant {name:<{name_pad}} : {signal_type} := {str(value)};') + write(f, 1, f"constant {name:<{name_pad}} : {signal_type} := {str(value)};") def type_declaration( @@ -188,7 +188,7 @@ def type_declaration( alias : str The type to tie the new name to. """ - write(f, 1, f'type {name} is {alias};') + write(f, 1, f"type {name} is {alias};") def process_prologue( @@ -214,10 +214,10 @@ def process_prologue( An optional name for the process. """ if name is not None: - write(f, indent, f'{name}: process({sensitivity_list})') + write(f, indent, f"{name}: process({sensitivity_list})") else: - write(f, indent, f'process({sensitivity_list})') - write(f, indent, 'begin') + write(f, indent, f"process({sensitivity_list})") + write(f, indent, "begin") def process_epilogue( @@ -243,10 +243,10 @@ def process_epilogue( An optional name of the ending process. """ _ = sensitivity_list - write(f, indent, 'end process', end="") + write(f, indent, "end process", end="") if name is not None: - write(f, 0, ' ' + name, end="") - write(f, 0, ';') + write(f, 0, " " + name, end="") + write(f, 0, ";") def synchronous_process_prologue( @@ -275,7 +275,7 @@ def synchronous_process_prologue( An optional name for the process. """ process_prologue(f, sensitivity_list=clk, indent=indent, name=name) - write(f, indent + 1, 'if rising_edge(clk) then') + write(f, indent + 1, "if rising_edge(clk) then") def synchronous_process_epilogue( @@ -302,7 +302,7 @@ def synchronous_process_epilogue( An optional name for the process. """ _ = clk - write(f, indent + 1, 'end if;') + write(f, indent + 1, "end if;") process_epilogue(f, sensitivity_list=clk, indent=indent, name=name) @@ -333,9 +333,9 @@ def synchronous_process( An optional name for the process. """ synchronous_process_prologue(f, clk, indent, name) - for line in body.split('\n'): + for line in body.split("\n"): if len(line): - write(f, indent + 2, f'{line}') + write(f, indent + 2, f"{line}") synchronous_process_epilogue(f, clk, indent, name) @@ -369,18 +369,18 @@ def synchronous_memory( write_lines( f, [ - (3, f'if {read_enable} = \'1\' then'), - (4, f'{read_name} <= memory({address});'), - (3, 'end if;'), + (3, f"if {read_enable} = '1' then"), + (4, f"{read_name} <= memory({address});"), + (3, "end if;"), ], ) for write_name, address, we in write_ports: write_lines( f, [ - (3, f'if {we} = \'1\' then'), - (4, f'memory({address}) <= {write_name};'), - (3, 'end if;'), + (3, f"if {we} = '1' then"), + (4, f"memory({address}) <= {write_name};"), + (3, "end if;"), ], ) synchronous_process_epilogue(f, clk=clk, name=name) @@ -416,14 +416,14 @@ def asynchronous_read_memory( write_lines( f, [ - (3, f'if {we} = \'1\' then'), - (4, f'memory({address}) <= {write_name};'), - (3, 'end if;'), + (3, f"if {we} = '1' then"), + (4, f"memory({address}) <= {write_name};"), + (3, "end if;"), ], ) synchronous_process_epilogue(f, clk=clk, name=name) for read_name, address, _ in read_ports: - write(f, 1, f'{read_name} <= memory({address});') + write(f, 1, f"{read_name} <= memory({address});") def is_valid_vhdl_identifier(identifier: str) -> bool: @@ -447,7 +447,7 @@ def is_valid_vhdl_identifier(identifier: str) -> bool: # * A basic identifier consists only of letters, digits, and underlines. # * A basic identifier is not a reserved VHDL keyword is_basic_identifier = ( - re.fullmatch(pattern=r'[a-zA-Z][0-9a-zA-Z_]*', string=identifier) is not None + re.fullmatch(pattern=r"[a-zA-Z][0-9a-zA-Z_]*", string=identifier) is not None ) return is_basic_identifier and not is_vhdl_reserved_keyword(identifier) diff --git a/b_asic/codegen/vhdl/entity.py b/b_asic/codegen/vhdl/entity.py index 40a7caf7..9a53e4a4 100644 --- a/b_asic/codegen/vhdl/entity.py +++ b/b_asic/codegen/vhdl/entity.py @@ -31,12 +31,12 @@ def memory_based_storage( write_lines( f, [ - (0, f'entity {entity_name} is'), - (1, 'generic('), - (2, '-- Data word length'), - (2, f'WL : integer := {word_length}'), - (1, ');'), - (1, 'port('), + (0, f"entity {entity_name} is"), + (1, "generic("), + (2, "-- Data word length"), + (2, f"WL : integer := {word_length}"), + (1, ");"), + (1, "port("), ], ) @@ -44,38 +44,38 @@ def memory_based_storage( write_lines( f, [ - (2, '-- Clock, synchronous reset and enable signals'), - (2, 'clk : in std_logic;'), - (2, 'rst : in std_logic;'), - (2, 'en : in std_logic;'), - (0, ''), + (2, "-- Clock, synchronous reset and enable signals"), + (2, "clk : in std_logic;"), + (2, "rst : in std_logic;"), + (2, "en : in std_logic;"), + (0, ""), ], ) # Write the input port specification - f.write(f'{2*VHDL_TAB}-- Memory port I/O\n') + f.write(f"{2 * VHDL_TAB}-- Memory port I/O\n") read_ports: set[Port] = { read_port for mv in collection for read_port in mv.read_ports } # type: ignore for read_port in read_ports: port_name = read_port if isinstance(read_port, int) else read_port.name - port_name = 'p_' + str(port_name) + '_in' - f.write(f'{2*VHDL_TAB}{port_name} : in std_logic_vector(WL-1 downto 0);\n') + port_name = "p_" + str(port_name) + "_in" + f.write(f"{2 * VHDL_TAB}{port_name} : in std_logic_vector(WL-1 downto 0);\n") # Write the output port specification write_ports: Set[Port] = {mv.write_port for mv in collection} # type: ignore for idx, write_port in enumerate(write_ports): port_name = write_port if isinstance(write_port, int) else write_port.name - port_name = 'p_' + str(port_name) + '_out' - f.write(f'{2*VHDL_TAB}{port_name} : out std_logic_vector(WL-1 downto 0)') + port_name = "p_" + str(port_name) + "_out" + f.write(f"{2 * VHDL_TAB}{port_name} : out std_logic_vector(WL-1 downto 0)") if idx == len(write_ports) - 1: - f.write('\n') + f.write("\n") else: - f.write(';\n') + f.write(";\n") # Write ending of the port header - f.write(f'{VHDL_TAB});\n') - f.write(f'end entity {entity_name};\n\n') + f.write(f"{VHDL_TAB});\n") + f.write(f"end entity {entity_name};\n\n") def register_based_storage( diff --git a/b_asic/core_operations.py b/b_asic/core_operations.py index faf5d31d..a879edec 100644 --- a/b_asic/core_operations.py +++ b/b_asic/core_operations.py @@ -1302,7 +1302,7 @@ class SymmetricTwoportAdaptor(AbstractOperation): if -1 <= value <= 1: self.set_param("value", value) else: - raise ValueError('value must be between -1 and 1 (inclusive)') + raise ValueError("value must be between -1 and 1 (inclusive)") def swap_io(self) -> None: # Swap inputs and outputs and change sign of coefficient diff --git a/b_asic/gui_utils/about_window.py b/b_asic/gui_utils/about_window.py index 893c1b74..91a7522d 100644 --- a/b_asic/gui_utils/about_window.py +++ b/b_asic/gui_utils/about_window.py @@ -52,11 +52,11 @@ class AboutWindow(QDialog): " Toolbox__\n\n*Construct, simulate and analyze signal processing" " algorithms aimed at implementation on an ASIC or" " FPGA.*\n\nB-ASIC is developed by the <a" - " href=\"https://liu.se/en/organisation/liu/isy/elda\">Division of" + ' href="https://liu.se/en/organisation/liu/isy/elda">Division of' " Electronics and Computer Engineering</a> at <a" - " href=\"https://liu.se/?l=en\">Linköping University</a>," + ' href="https://liu.se/?l=en">Linköping University</a>,' " Sweden.\n\nB-ASIC is released under the <a" - " href=\"https://gitlab.liu.se/da/B-ASIC/-/blob/master/LICENSE\">" + ' href="https://gitlab.liu.se/da/B-ASIC/-/blob/master/LICENSE">' "MIT-license</a>" " and any extension to the program should follow that same" f" license.\n\n*Version: {__version__}*\n\nCopyright 2020-2025," @@ -78,10 +78,10 @@ class AboutWindow(QDialog): label3 = QLabel( 'Additional resources: <a href="https://da.gitlab-pages.liu.se/B-ASIC/">' - 'documentation</a>,' + "documentation</a>," ' <a href="https://gitlab.liu.se/da/B-ASIC/">git repository</a>,' ' <a href="https://gitlab.liu.se/da/B-ASIC/-/issues">report issues and' - ' suggestions</a>.' + " suggestions</a>." ) label3.setOpenExternalLinks(True) label3.linkHovered.connect(self._hover_text) diff --git a/b_asic/gui_utils/color_button.py b/b_asic/gui_utils/color_button.py index 56e5058a..b47a248b 100644 --- a/b_asic/gui_utils/color_button.py +++ b/b_asic/gui_utils/color_button.py @@ -19,7 +19,7 @@ class ColorButton(QPushButton): Additional arguments are passed to QPushButton. """ - __slots__ = ('_color', '_default') + __slots__ = ("_color", "_default") _color: None | QColor _color_changed = Signal(QColor) diff --git a/b_asic/gui_utils/icons.py b/b_asic/gui_utils/icons.py index 4bd348a8..2940e1c7 100644 --- a/b_asic/gui_utils/icons.py +++ b/b_asic/gui_utils/icons.py @@ -3,39 +3,39 @@ import qtawesome ICONS = { - 'save': 'mdi6.content-save', - 'save-as': 'mdi6.content-save-edit', - 'undo': 'mdi6.undo', - 'redo': 'mdi6.redo', - 'new': 'mdi6.file-outline', - 'open': 'mdi6.folder-open', - 'import': 'mdi6.import', - 'legend': 'mdi6.map-legend', - 'close': 'mdi6.close', - 'all': 'mdi6.select-all', - 'none': 'mdi6.select-remove', - 'new-sfg': 'ph.selection-plus', - 'plot-schedule': 'mdi6.chart-gantt', - 'increase-timeresolution': 'ph.clock-clockwise', - 'decrease-timeresolution': 'ph.clock-counter-clockwise', - 'quit': 'ph.power', - 'info': 'ph.info', - 'gitlab': 'ph.gitlab-logo-simple', - 'docs': 'ph.book', - 'about': 'ph.question', - 'keys': 'ph.keyboard', - 'add-operations': 'ph.math-operations', - 'zoom-to-fit': 'mdi6.fit-to-page', - 'faq': 'mdi6.frequently-asked-questions', - 'sim': 'mdi6.chart-line', - 'reorder': ('msc.graph-left', {'rotated': -90}), - 'full-screen': 'mdi6.fullscreen', - 'full-screen-exit': 'mdi6.fullscreen-exit', - 'warning': 'fa5s.exclamation-triangle', - 'port-numbers': 'fa5s.hashtag', - 'swap': 'fa5s.arrows-alt-v', - 'asap': 'fa5s.fast-backward', - 'alap': 'fa5s.fast-forward', + "save": "mdi6.content-save", + "save-as": "mdi6.content-save-edit", + "undo": "mdi6.undo", + "redo": "mdi6.redo", + "new": "mdi6.file-outline", + "open": "mdi6.folder-open", + "import": "mdi6.import", + "legend": "mdi6.map-legend", + "close": "mdi6.close", + "all": "mdi6.select-all", + "none": "mdi6.select-remove", + "new-sfg": "ph.selection-plus", + "plot-schedule": "mdi6.chart-gantt", + "increase-timeresolution": "ph.clock-clockwise", + "decrease-timeresolution": "ph.clock-counter-clockwise", + "quit": "ph.power", + "info": "ph.info", + "gitlab": "ph.gitlab-logo-simple", + "docs": "ph.book", + "about": "ph.question", + "keys": "ph.keyboard", + "add-operations": "ph.math-operations", + "zoom-to-fit": "mdi6.fit-to-page", + "faq": "mdi6.frequently-asked-questions", + "sim": "mdi6.chart-line", + "reorder": ("msc.graph-left", {"rotated": -90}), + "full-screen": "mdi6.fullscreen", + "full-screen-exit": "mdi6.fullscreen-exit", + "warning": "fa5s.exclamation-triangle", + "port-numbers": "fa5s.hashtag", + "swap": "fa5s.arrows-alt-v", + "asap": "fa5s.fast-backward", + "alap": "fa5s.fast-forward", } diff --git a/b_asic/gui_utils/plot_window.py b/b_asic/gui_utils/plot_window.py index 4a9b0d58..24220332 100644 --- a/b_asic/gui_utils/plot_window.py +++ b/b_asic/gui_utils/plot_window.py @@ -77,7 +77,7 @@ class PlotWindow(QWidget): m = 4 elif re.fullmatch("[0-9]+", key): m = 3 - key2 = 'o' + key + key2 = "o" + key elif re.fullmatch("t[0-9]+", key): m = 2 else: @@ -91,18 +91,18 @@ class PlotWindow(QWidget): initially_checked.append(key2) else: # The same again, but split into several lines - dict_to_sort[m + 1 / (n + 1)] = key2 + '_re' - dict_to_sort[m + 1 / (n + 2)] = key2 + '_im' - dict_to_sort[m + 1 / (n + 3)] = key2 + '_mag' - dict_to_sort[m + 1 / (n + 4)] = key2 + '_ang' - updated_result[key2 + '_re'] = np.real(result) - updated_result[key2 + '_im'] = np.imag(result) - updated_result[key2 + '_mag'] = np.absolute(result) - updated_result[key2 + '_ang'] = np.angle(result) + dict_to_sort[m + 1 / (n + 1)] = key2 + "_re" + dict_to_sort[m + 1 / (n + 2)] = key2 + "_im" + dict_to_sort[m + 1 / (n + 3)] = key2 + "_mag" + dict_to_sort[m + 1 / (n + 4)] = key2 + "_ang" + updated_result[key2 + "_re"] = np.real(result) + updated_result[key2 + "_im"] = np.imag(result) + updated_result[key2 + "_mag"] = np.absolute(result) + updated_result[key2 + "_ang"] = np.angle(result) n = n + 4 if m == 3: # output - initially_checked.append(key2 + '_re') - initially_checked.append(key2 + '_im') + initially_checked.append(key2 + "_re") + initially_checked.append(key2 + "_im") key_order = list(dict(sorted(dict_to_sort.items(), reverse=True)).values()) @@ -145,10 +145,10 @@ class PlotWindow(QWidget): # Add two buttons for selecting all/none: hlayout = QHBoxLayout() - self._button_all = QPushButton(get_icon('all'), "&All") + self._button_all = QPushButton(get_icon("all"), "&All") self._button_all.clicked.connect(self._button_all_click) hlayout.addWidget(self._button_all) - self._button_none = QPushButton(get_icon('none'), "&None") + self._button_none = QPushButton(get_icon("none"), "&None") self._button_none.clicked.connect(self._button_none_click) hlayout.addWidget(self._button_none) listlayout.addLayout(hlayout) @@ -173,7 +173,7 @@ class PlotWindow(QWidget): self._legend_checkbox = QCheckBox("&Legend") self._legend_checkbox.stateChanged.connect(self._legend_checkbox_change) self._legend_checkbox.setCheckState(Qt.CheckState.Checked) - self._legend_checkbox.setIcon(get_icon('legend')) + self._legend_checkbox.setIcon(get_icon("legend")) listlayout.addWidget(self._legend_checkbox) # self.ontop_checkbox = QCheckBox("&On top") # self.ontop_checkbox.stateChanged.connect(self._ontop_checkbox_change) @@ -185,7 +185,7 @@ class PlotWindow(QWidget): listlayout.addWidget(relim_button) # Add "Close" buttons - button_close = QPushButton(get_icon('close'), "&Close", self) + button_close = QPushButton(get_icon("close"), "&Close", self) button_close.clicked.connect(self.close) listlayout.addWidget(button_close) self._relim() @@ -231,8 +231,8 @@ class PlotWindow(QWidget): def _relim(self, event=None): self._plot_axes.relim(True) self._plot_axes.autoscale(True) - self._plot_axes.autoscale(axis='x', tight=True) - self._plot_axes.autoscale(axis='y') + self._plot_axes.autoscale(axis="x", tight=True) + self._plot_axes.autoscale(axis="y") self._plot_canvas.draw() @@ -263,15 +263,15 @@ def start_simulation_dialog( # Simple test of the dialog if __name__ == "__main__": sim_res = { - '0': [0.5, 0.6, 0.5, 0], - '1': [0.0, 1.0 + 0.3j, 0.5, 0.1j], - 'add1': [0.5, 0.5, 0, 0], - 'cmul1': [0, 0.5, 0, 0], - 'cmul2': [0.5, 0, 0, 0], - 'in1': [1, 0, 0, 0], - 'in2': [0.1, 2, 0, 0], - 't1': [0, 1, 0, 0], - 't2': [0, 0, 1, 0], - 't3': [0, 0, 0, 1], + "0": [0.5, 0.6, 0.5, 0], + "1": [0.0, 1.0 + 0.3j, 0.5, 0.1j], + "add1": [0.5, 0.5, 0, 0], + "cmul1": [0, 0.5, 0, 0], + "cmul2": [0.5, 0, 0, 0], + "in1": [1, 0, 0, 0], + "in2": [0.1, 2, 0, 0], + "t1": [0, 1, 0, 0], + "t2": [0, 0, 1, 0], + "t3": [0, 0, 0, 1], } start_simulation_dialog(sim_res, "Test data") diff --git a/b_asic/logger.py b/b_asic/logger.py index 2a948ce3..96f89df6 100644 --- a/b_asic/logger.py +++ b/b_asic/logger.py @@ -46,6 +46,7 @@ Log Uncaught Exceptions: ------------------------ To log uncaught exceptions, implement the following in your program. `sys.excepthook = logger.log_exceptions`""" + import logging import logging.handlers import os diff --git a/b_asic/research/interleaver.py b/b_asic/research/interleaver.py index 296accea..1232c2bf 100644 --- a/b_asic/research/interleaver.py +++ b/b_asic/research/interleaver.py @@ -108,7 +108,7 @@ def generate_matrix_transposer( if (rows * cols // parallelism) * parallelism != rows * cols: raise ValueError( f"parallelism ({parallelism}) must be an integer multiple of rows*cols" - f" ({rows}*{cols} = {rows*cols})" + f" ({rows}*{cols} = {rows * cols})" ) inputorders = [] @@ -129,7 +129,7 @@ def generate_matrix_transposer( PlainMemoryVariable( *inputorder, {outputorders[i][1]: outputorders[i][0] - inputorder[0]}, - name=f"{inputorders[i][0]*parallelism + inputorders[i][1]}", + name=f"{inputorders[i][0] * parallelism + inputorders[i][1]}", ) for i, inputorder in enumerate(inputorders) }, diff --git a/b_asic/resources.py b/b_asic/resources.py index 53a5ef25..36e2cec9 100644 --- a/b_asic/resources.py +++ b/b_asic/resources.py @@ -32,7 +32,7 @@ _WARNING_COLOR = tuple(c / 255 for c in WARNING_COLOR) # # Typing '_T' to help Pyright propagate type-information # -_T = TypeVar('_T') +_T = TypeVar("_T") def _sorted_nicely(to_be_sorted: Iterable[_T]) -> list[_T]: @@ -42,7 +42,7 @@ def _sorted_nicely(to_be_sorted: Iterable[_T]) -> list[_T]: return int(text) if text.isdigit() else text def alphanum_key(key): - return [convert(c) for c in re.split('([0-9]+)', str(key))] + return [convert(c) for c in re.split("([0-9]+)", str(key))] return sorted(to_be_sorted, key=alphanum_key) @@ -85,11 +85,11 @@ def _sanitize_port_option( write_ports = total_ports if write_ports is None else write_ports if total_ports < read_ports: raise ValueError( - f'Total ports ({total_ports}) less then read ports ({read_ports})' + f"Total ports ({total_ports}) less then read ports ({read_ports})" ) if total_ports < write_ports: raise ValueError( - f'Total ports ({total_ports}) less then write ports ({write_ports})' + f"Total ports ({total_ports}) less then write ports ({write_ports})" ) return read_ports, write_ports, total_ports @@ -142,23 +142,23 @@ def draw_exclusion_graph_coloring( None """ COLOR_LIST = [ - '#aa0000', - '#00aa00', - '#0000ff', - '#ff00aa', - '#ffaa00', - '#ffffff', - '#00ffaa', - '#aaff00', - '#aa00ff', - '#00aaff', - '#ff0000', - '#00ff00', - '#0000aa', - '#aaaa00', - '#aa00aa', - '#00aaaa', - '#666666', + "#aa0000", + "#00aa00", + "#0000ff", + "#ff00aa", + "#ffaa00", + "#ffffff", + "#00ffaa", + "#aaff00", + "#aa00ff", + "#00aaff", + "#ff0000", + "#00ff00", + "#0000aa", + "#aaaa00", + "#aa00aa", + "#00aaaa", + "#666666", ] if color_list is None: node_color_dict = {k: COLOR_LIST[v] for k, v in color_dict.items()} @@ -216,7 +216,7 @@ class _ForwardBackwardEntry: class _ForwardBackwardTable: - def __init__(self, collection: 'ProcessCollection'): + def __init__(self, collection: "ProcessCollection"): """ Forward-Backward allocation table for ProcessCollections. @@ -299,9 +299,9 @@ class _ForwardBackwardTable: if self.table[next_row].regs[next_col] not in (None, reg): cell = self.table[next_row].regs[next_col] raise ValueError( - f'Can\'t forward allocate {reg} in row={time},' - f' col={reg_idx} to next_row={next_row},' - f' next_col={next_col} (cell contains: {cell})' + f"Can't forward allocate {reg} in row={time}," + f" col={reg_idx} to next_row={next_row}," + f" next_col={next_col} (cell contains: {cell})" ) else: self.table[(time + 1) % rows].regs[reg_idx + 1] = reg @@ -380,27 +380,27 @@ class _ForwardBackwardTable: reg_col_w = max(4, reg_col_w + 2) # Header row of the string - res = f' T |{"In":^{input_col_w}}|' + res = f" T |{'In':^{input_col_w}}|" for i in range(max(self._live_variables)): - reg = f'R{i}' - res += f'{reg:^{reg_col_w}}|' - res += f'{"Out":^{output_col_w}}|' - res += '\n' + reg = f"R{i}" + res += f"{reg:^{reg_col_w}}|" + res += f"{'Out':^{output_col_w}}|" + res += "\n" res += ( 6 + input_col_w + (reg_col_w + 1) * max(self._live_variables) + output_col_w - ) * '-' + '\n' + ) * "-" + "\n" for time, entry in enumerate(self.table): # Time - res += f'{time:^3}| ' + res += f"{time:^3}| " # Input column - inputs_str = '' + inputs_str = "" for input_ in entry.inputs: - inputs_str += input_.name + ',' + inputs_str += input_.name + "," if inputs_str: inputs_str = inputs_str[:-1] - res += f'{inputs_str:^{input_col_w-1}}|' + res += f"{inputs_str:^{input_col_w - 1}}|" # Register columns for reg_idx, reg in enumerate(entry.regs): @@ -408,27 +408,27 @@ class _ForwardBackwardTable: res += " " * reg_col_w + "|" else: if reg_idx in entry.back_edge_to: - res += f'{GREEN_BACKGROUND_ANSI}' - res += f'{reg.name:^{reg_col_w}}' - res += f'{RESET_BACKGROUND_ANSI}|' + res += f"{GREEN_BACKGROUND_ANSI}" + res += f"{reg.name:^{reg_col_w}}" + res += f"{RESET_BACKGROUND_ANSI}|" elif reg_idx in entry.back_edge_from: - res += f'{BROWN_BACKGROUND_ANSI}' - res += f'{reg.name:^{reg_col_w}}' - res += f'{RESET_BACKGROUND_ANSI}|' + res += f"{BROWN_BACKGROUND_ANSI}" + res += f"{reg.name:^{reg_col_w}}" + res += f"{RESET_BACKGROUND_ANSI}|" else: - res += f'{reg.name:^{reg_col_w}}' + "|" + res += f"{reg.name:^{reg_col_w}}" + "|" # Output column - outputs_str = '' + outputs_str = "" for output in entry.outputs: - outputs_str += output.name + ',' + outputs_str += output.name + "," if outputs_str: outputs_str = outputs_str[:-1] if entry.outputs_from is not None: outputs_str += f"({entry.outputs_from})" - res += f'{outputs_str:^{output_col_w}}|' + res += f"{outputs_str:^{output_col_w}}|" - res += '\n' + res += "\n" return res @@ -591,8 +591,8 @@ class ProcessCollection: # Schedule time needs to be greater than or equal to the maximum process # lifetime raise ValueError( - f'Schedule time: {self._schedule_time} < Max execution' - f' time: {max_execution_time}' + f"Schedule time: {self._schedule_time} < Max execution" + f" time: {max_execution_time}" ) # Generate the life-time chart @@ -673,12 +673,8 @@ class ProcessCollection: ) _ax.grid(True) # type: ignore - _ax.xaxis.set_major_locator( - MaxNLocator(integer=True, min_n_ticks=1) - ) # type: ignore - _ax.yaxis.set_major_locator( - MaxNLocator(integer=True, min_n_ticks=1) - ) # type: ignore + _ax.xaxis.set_major_locator(MaxNLocator(integer=True, min_n_ticks=1)) # type: ignore + _ax.yaxis.set_major_locator(MaxNLocator(integer=True, min_n_ticks=1)) # type: ignore _ax.set_xlim(0, self._schedule_time) # type: ignore if row is None: _ax.set_ylim(0.25, len(self._collection) + 0.75) # type: ignore @@ -1260,7 +1256,7 @@ class ProcessCollection: filename: str, entity_name: str, word_length: int, - assignment: list['ProcessCollection'], + assignment: list["ProcessCollection"], read_ports: int = 1, write_ports: int = 1, total_ports: int = 2, @@ -1312,7 +1308,7 @@ class ProcessCollection: """ # Check that entity name is a valid VHDL identifier if not is_valid_vhdl_identifier(entity_name): - raise KeyError(f'{entity_name} is not a valid identifier') + raise KeyError(f"{entity_name} is not a valid identifier") # Check that this is a ProcessCollection of (Plain)MemoryVariables is_memory_variable = all( @@ -1337,51 +1333,51 @@ class ProcessCollection: for collection in assignment: for mv in collection: if mv not in self: - raise ValueError(f'{mv!r} is not part of {self!r}.') + raise ValueError(f"{mv!r} is not part of {self!r}.") # Make sure that concurrent reads/writes do not surpass the port setting needed_write_ports = self.read_ports_bound() needed_read_ports = self.write_ports_bound() if needed_write_ports > write_ports + 1: raise ValueError( - f'More than {write_ports} write ports needed ({needed_write_ports})' - ' to generate HDL for this ProcessCollection' + f"More than {write_ports} write ports needed ({needed_write_ports})" + " to generate HDL for this ProcessCollection" ) if needed_read_ports > read_ports + 1: raise ValueError( - f'More than {read_ports} read ports needed ({needed_read_ports}) to' - ' generate HDL for this ProcessCollection' + f"More than {read_ports} read ports needed ({needed_read_ports}) to" + " generate HDL for this ProcessCollection" ) # Sanitize the address logic pipeline settings if adr_mux_size is not None and adr_pipe_depth is not None: if adr_mux_size < 1: raise ValueError( - f'adr_mux_size={adr_mux_size} need to be greater than zero' + f"adr_mux_size={adr_mux_size} need to be greater than zero" ) if adr_pipe_depth < 0: raise ValueError( - f'adr_pipe_depth={adr_pipe_depth} needs to be non-negative' + f"adr_pipe_depth={adr_pipe_depth} needs to be non-negative" ) if not input_sync: - raise ValueError('input_sync needs to be set to use address pipelining') + raise ValueError("input_sync needs to be set to use address pipelining") if not log2(adr_mux_size).is_integer(): raise ValueError( - f'adr_mux_size={adr_mux_size} needs to be integer power of two' + f"adr_mux_size={adr_mux_size} needs to be integer power of two" ) if adr_mux_size**adr_pipe_depth > assignment[0].schedule_time: raise ValueError( - f'adr_mux_size={adr_mux_size}, adr_pipe_depth={adr_pipe_depth} => ' - 'more multiplexer inputs than schedule_time=' - f'{assignment[0].schedule_time}' + f"adr_mux_size={adr_mux_size}, adr_pipe_depth={adr_pipe_depth} => " + "more multiplexer inputs than schedule_time=" + f"{assignment[0].schedule_time}" ) else: if adr_mux_size is not None or adr_pipe_depth is not None: raise ValueError( - 'both or none of adr_mux_size and adr_pipe_depth needs to be set' + "both or none of adr_mux_size and adr_pipe_depth needs to be set" ) - with open(filename, 'w') as f: + with open(filename, "w") as f: from b_asic.codegen.vhdl import architecture, common, entity common.b_asic_preamble(f) @@ -1483,7 +1479,7 @@ class ProcessCollection: """ # Check that entity name is a valid VHDL identifier if not is_valid_vhdl_identifier(entity_name): - raise KeyError(f'{entity_name} is not a valid identifier') + raise KeyError(f"{entity_name} is not a valid identifier") # Check that this is a ProcessCollection of (Plain)MemoryVariables is_memory_variable = all( @@ -1506,7 +1502,7 @@ class ProcessCollection: # Create the forward-backward table forward_backward_table = _ForwardBackwardTable(self) - with open(filename, 'w') as f: + with open(filename, "w") as f: from b_asic.codegen.vhdl import architecture, common, entity common.b_asic_preamble(f) @@ -1666,4 +1662,4 @@ class ProcessCollection: if name in name_to_proc: return name_to_proc[name] else: - raise KeyError(f'{name} not in {self}') + raise KeyError(f"{name} not in {self}") diff --git a/b_asic/schedule.py b/b_asic/schedule.py index 33a629b3..1a70a84a 100644 --- a/b_asic/schedule.py +++ b/b_asic/schedule.py @@ -1199,7 +1199,7 @@ class Schedule: ) path_patch = PathPatch( path, - fc='none', + fc="none", ec=_SIGNAL_COLOR, lw=SIGNAL_LINEWIDTH, zorder=10, diff --git a/b_asic/scheduler_gui/__init__.py b/b_asic/scheduler_gui/__init__.py index 65a54cad..b1a11a86 100644 --- a/b_asic/scheduler_gui/__init__.py +++ b/b_asic/scheduler_gui/__init__.py @@ -6,4 +6,4 @@ Graphical user interface for B-ASIC scheduler. from b_asic.scheduler_gui.main_window import start_scheduler -__all__ = ['start_scheduler'] +__all__ = ["start_scheduler"] diff --git a/b_asic/scheduler_gui/_preferences.py b/b_asic/scheduler_gui/_preferences.py index 90da43a7..abe72305 100644 --- a/b_asic/scheduler_gui/_preferences.py +++ b/b_asic/scheduler_gui/_preferences.py @@ -29,7 +29,7 @@ class ColorDataType: DEFAULT: QColor, current_color: QColor = SIGNAL_INACTIVE, changed: bool = False, - name: str = '', + name: str = "", ): self.current_color = current_color self.DEFAULT = DEFAULT @@ -40,21 +40,21 @@ class ColorDataType: LATENCY_COLOR_TYPE = ColorDataType( current_color=OPERATION_LATENCY_INACTIVE, DEFAULT=OPERATION_LATENCY_INACTIVE, - name='Latency color', + name="Latency color", ) EXECUTION_TIME_COLOR_TYPE = ColorDataType( current_color=OPERATION_EXECUTION_TIME_ACTIVE, DEFAULT=OPERATION_EXECUTION_TIME_ACTIVE, - name='Execution time color', + name="Execution time color", ) SIGNAL_WARNING_COLOR_TYPE = ColorDataType( - current_color=SIGNAL_WARNING, DEFAULT=SIGNAL_WARNING, name='Warning color' + current_color=SIGNAL_WARNING, DEFAULT=SIGNAL_WARNING, name="Warning color" ) SIGNAL_COLOR_TYPE = ColorDataType( - current_color=SIGNAL_INACTIVE, DEFAULT=SIGNAL_INACTIVE, name='Signal color' + current_color=SIGNAL_INACTIVE, DEFAULT=SIGNAL_INACTIVE, name="Signal color" ) ACTIVE_COLOR_TYPE = ColorDataType( - current_color=SIGNAL_ACTIVE, DEFAULT=SIGNAL_ACTIVE, name='Active color' + current_color=SIGNAL_ACTIVE, DEFAULT=SIGNAL_ACTIVE, name="Active color" ) diff --git a/b_asic/scheduler_gui/axes_item.py b/b_asic/scheduler_gui/axes_item.py index f65b11c0..52e143f1 100644 --- a/b_asic/scheduler_gui/axes_item.py +++ b/b_asic/scheduler_gui/axes_item.py @@ -5,6 +5,7 @@ B-ASIC Scheduler-gui Axes Item Module. Contains the scheduler-gui AxesItem class for drawing and maintaining the axes in a graph. """ + from math import pi, sin # QGraphics and QPainter imports diff --git a/b_asic/scheduler_gui/main_window.py b/b_asic/scheduler_gui/main_window.py index b5ba1a02..b2950268 100644 --- a/b_asic/scheduler_gui/main_window.py +++ b/b_asic/scheduler_gui/main_window.py @@ -6,6 +6,7 @@ Contains the scheduler_gui MainWindow class for scheduling operations in an SFG. Start main-window with ``start_gui()``. """ + import inspect import os import pickle @@ -31,28 +32,19 @@ from qtpy.QtCore import ( Qt, Slot, ) -from qtpy.QtGui import QCloseEvent, QColor, QFont, QIcon, QIntValidator, QPalette +from qtpy.QtGui import QCloseEvent, QColor, QPalette from qtpy.QtWidgets import ( QAbstractButton, QAction, QApplication, QCheckBox, - QColorDialog, - QDialog, - QDialogButtonBox, QFileDialog, - QFontDialog, QGraphicsItemGroup, QGraphicsScene, - QGroupBox, - QHBoxLayout, QInputDialog, - QLabel, - QLineEdit, QMainWindow, QMessageBox, QTableWidgetItem, - QVBoxLayout, ) # B-ASIC @@ -60,24 +52,18 @@ import b_asic.logger as logger from b_asic._version import __version__ from b_asic.graph_component import GraphComponent, GraphID from b_asic.gui_utils.about_window import AboutWindow -from b_asic.gui_utils.color_button import ColorButton from b_asic.gui_utils.icons import get_icon from b_asic.gui_utils.mpl_window import MPLWindow from b_asic.schedule import Schedule from b_asic.scheduler_gui._preferences import ( - ACTIVE_COLOR_TYPE, - EXECUTION_TIME_COLOR_TYPE, FONT, LATENCY_COLOR_TYPE, - SIGNAL_COLOR_TYPE, - SIGNAL_WARNING_COLOR_TYPE, - ColorDataType, read_from_settings, - reset_color_settings, write_to_settings, ) from b_asic.scheduler_gui.axes_item import AxesItem from b_asic.scheduler_gui.operation_item import OperationItem +from b_asic.scheduler_gui.preferences_dialog import PreferencesDialog from b_asic.scheduler_gui.scheduler_item import SchedulerItem from b_asic.scheduler_gui.ui_main_window import Ui_MainWindow @@ -166,31 +152,31 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): # Connect signals to slots self.menu_load_from_file.triggered.connect(self._load_schedule_from_pyfile) - self.menu_load_from_file.setIcon(get_icon('import')) - self.menu_open.setIcon(get_icon('open')) + self.menu_load_from_file.setIcon(get_icon("import")) + self.menu_open.setIcon(get_icon("open")) self.menu_open.triggered.connect(self.open_schedule) self.menu_close_schedule.triggered.connect(self.close_schedule) - self.menu_close_schedule.setIcon(get_icon('close')) + self.menu_close_schedule.setIcon(get_icon("close")) self.menu_save.triggered.connect(self.save) - self.menu_save.setIcon(get_icon('save')) + self.menu_save.setIcon(get_icon("save")) self.menu_save_as.triggered.connect(self.save_as) - self.menu_save_as.setIcon(get_icon('save-as')) + self.menu_save_as.setIcon(get_icon("save-as")) self.actionPreferences.triggered.connect(self.open_preferences_dialog) self.menu_quit.triggered.connect(self.close) - self.menu_quit.setIcon(get_icon('quit')) + self.menu_quit.setIcon(get_icon("quit")) self.menu_node_info.triggered.connect(self.show_info_table) - self.menu_node_info.setIcon(get_icon('info')) + self.menu_node_info.setIcon(get_icon("info")) self.menu_exit_dialog.triggered.connect(self.hide_exit_dialog) self.actionReorder.triggered.connect(self._action_reorder) - self.actionReorder.setIcon(get_icon('reorder')) + self.actionReorder.setIcon(get_icon("reorder")) self.actionStatus_bar.triggered.connect(self._toggle_statusbar) - self.action_incorrect_execution_time.setIcon(get_icon('warning')) + self.action_incorrect_execution_time.setIcon(get_icon("warning")) self.action_incorrect_execution_time.triggered.connect( self._toggle_execution_time_warning ) - self.action_show_port_numbers.setIcon(get_icon('port-numbers')) + self.action_show_port_numbers.setIcon(get_icon("port-numbers")) self.action_show_port_numbers.triggered.connect(self._toggle_port_number) - self.actionPlot_schedule.setIcon(get_icon('plot-schedule')) + self.actionPlot_schedule.setIcon(get_icon("plot-schedule")) self.actionPlot_schedule.triggered.connect(self._plot_schedule) self.action_view_variables.triggered.connect( self._show_execution_times_for_variables @@ -198,25 +184,25 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): self.action_view_port_accesses.triggered.connect( self._show_ports_accesses_for_storage ) - self.actionZoom_to_fit.setIcon(get_icon('zoom-to-fit')) + self.actionZoom_to_fit.setIcon(get_icon("zoom-to-fit")) self.actionZoom_to_fit.triggered.connect(self._zoom_to_fit) - self.actionToggle_full_screen.setIcon(get_icon('full-screen')) + self.actionToggle_full_screen.setIcon(get_icon("full-screen")) self.actionToggle_full_screen.triggered.connect(self._toggle_fullscreen) - self.actionUndo.setIcon(get_icon('undo')) - self.actionRedo.setIcon(get_icon('redo')) + self.actionUndo.setIcon(get_icon("undo")) + self.actionRedo.setIcon(get_icon("redo")) self.splitter.splitterMoved.connect(self._splitter_moved) self.actionDocumentation.triggered.connect(self._open_documentation) - self.actionDocumentation.setIcon(get_icon('docs')) + self.actionDocumentation.setIcon(get_icon("docs")) self.actionAbout.triggered.connect(self._open_about_window) - self.actionAbout.setIcon(get_icon('about')) + self.actionAbout.setIcon(get_icon("about")) self.actionDecrease_time_resolution.triggered.connect( self._decrease_time_resolution ) - self.actionDecrease_time_resolution.setIcon(get_icon('decrease-timeresolution')) + self.actionDecrease_time_resolution.setIcon(get_icon("decrease-timeresolution")) self.actionIncrease_time_resolution.triggered.connect( self._increase_time_resolution ) - self.actionIncrease_time_resolution.setIcon(get_icon('increase-timeresolution')) + self.actionIncrease_time_resolution.setIcon(get_icon("increase-timeresolution")) # Setup event member functions self.closeEvent = self._close_event @@ -468,7 +454,7 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): self.save_as() return self._schedule._sfg._graph_id_generator = None - with open(self._file_name, 'wb') as f: + with open(self._file_name, "wb") as f: pickle.dump(self._schedule, f) self._add_recent_file(self._file_name) self.update_statusbar(self.tr("Schedule saved successfully")) @@ -482,15 +468,15 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): """ # TODO: Implement filename, extension = QFileDialog.getSaveFileName( - self, 'Save File', '.', filter=self.tr("B-ASIC schedule (*.bsc)") + self, "Save File", ".", filter=self.tr("B-ASIC schedule (*.bsc)") ) if not filename: return - if not filename.endswith('.bsc'): - filename += '.bsc' + if not filename.endswith(".bsc"): + filename += ".bsc" self._file_name = filename self._schedule._sfg._graph_id_generator = None - with open(self._file_name, 'wb') as f: + with open(self._file_name, "wb") as f: pickle.dump(self._schedule, f) self._add_recent_file(self._file_name) self.update_statusbar(self.tr("Schedule saved successfully")) @@ -521,7 +507,7 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): self._file_name = abs_path_filename self._add_recent_file(abs_path_filename) - with open(self._file_name, 'rb') as f: + with open(self._file_name, "rb") as f: schedule = pickle.load(f) self.open(schedule) settings = QSettings() @@ -908,250 +894,69 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): @Slot() def open_preferences_dialog(self): """Open the preferences dialog to customize fonts, colors, and settings""" - dialog = QDialog() - dialog.setWindowTitle("Preferences") - layout = QVBoxLayout() - layout.setSpacing(15) - - # Add label for the dialog - label = QLabel("Customize fonts and colors") - layout.addWidget(label) - - groupbox = QGroupBox() - hlayout = QHBoxLayout() - label = QLabel("Color settings:") - layout.addWidget(label) - hlayout.setSpacing(20) - - hlayout.addWidget(self.create_color_button(EXECUTION_TIME_COLOR_TYPE)) - hlayout.addWidget(self.create_color_button(LATENCY_COLOR_TYPE)) - hlayout.addWidget( - self.create_color_button( - ColorDataType( - current_color=LATENCY_COLOR_TYPE.DEFAULT, - DEFAULT=QColor('skyblue'), - name="Latency color per type", - ) - ) - ) - - groupbox.setLayout(hlayout) - layout.addWidget(groupbox) - - label = QLabel("Signal colors:") - layout.addWidget(label) - groupbox = QGroupBox() - hlayout = QHBoxLayout() - hlayout.setSpacing(20) - - signal_color_button = self.create_color_button(SIGNAL_COLOR_TYPE) - signal_color_button.setStyleSheet( - f"color: {QColor(255,255,255,0).name()}; background-color: {SIGNAL_COLOR_TYPE.DEFAULT.name()}" - ) - - hlayout.addWidget(signal_color_button) - hlayout.addWidget(self.create_color_button(SIGNAL_WARNING_COLOR_TYPE)) - hlayout.addWidget(self.create_color_button(ACTIVE_COLOR_TYPE)) - - groupbox.setLayout(hlayout) - layout.addWidget(groupbox) - - reset_color_button = ColorButton(QColor('silver')) - reset_color_button.setText('Reset All Color settings') - reset_color_button.pressed.connect(self.reset_color_clicked) - layout.addWidget(reset_color_button) - - label = QLabel("Font Settings:") - layout.addWidget(label) - - groupbox = QGroupBox() - hlayout = QHBoxLayout() - hlayout.setSpacing(10) - - font_button = ColorButton(QColor('moccasin')) - font_button.setText('Font Settings') - hlayout.addWidget(font_button) - - font_color_button = ColorButton(QColor('moccasin')) - font_color_button.setText('Font Color') - font_color_button.pressed.connect(self.font_color_clicked) - hlayout.addWidget(font_color_button) - - groupbox2 = QGroupBox() - hlayout2 = QHBoxLayout() - - icon = QIcon.fromTheme("format-text-italic") - italic_button = ( - ColorButton(QColor('silver')) - if FONT.italic - else ColorButton(QColor('snow')) - ) - italic_button.setIcon(icon) - italic_button.pressed.connect(lambda: self.italic_font_clicked(italic_button)) - hlayout2.addWidget(italic_button) - - icon = QIcon.fromTheme("format-text-bold") - bold_button = ( - ColorButton(QColor('silver')) if FONT.bold else ColorButton(QColor('snow')) - ) - bold_button.setIcon(icon) - bold_button.pressed.connect(lambda: self.bold_font_clicked(bold_button)) - hlayout2.addWidget(bold_button) - - groupbox2.setLayout(hlayout2) - hlayout.addWidget(groupbox2) - - groupbox2 = QGroupBox() - hlayout2 = QHBoxLayout() - font_size_input = QLineEdit() - font_button.pressed.connect( - lambda: self.font_clicked(font_size_input, italic_button, bold_button) - ) - - icon = QIcon.fromTheme("list-add") - increase_font_size_button = ColorButton(QColor('smoke')) - increase_font_size_button.setIcon(icon) - increase_font_size_button.pressed.connect( - lambda: self.increase_font_size_clicked(font_size_input) - ) - increase_font_size_button.setShortcut( - QCoreApplication.translate("MainWindow", "Ctrl++") - ) - hlayout2.addWidget(increase_font_size_button) - - font_size_input.setPlaceholderText('Font Size') - font_size_input.setText(f'Font Size: {FONT.size}') - font_size_input.setValidator(QIntValidator(0, 99)) - font_size_input.setAlignment(Qt.AlignCenter) - font_size_input.textChanged.connect( - lambda: self.set_font_size_clicked(font_size_input.text()) - ) - font_size_input.textChanged.connect( - lambda: self.set_font_size_clicked(font_size_input.text()) - ) - hlayout2.addWidget(font_size_input) + self._preferences_dialog = PreferencesDialog(self) + self._preferences_dialog.show() - icon = QIcon.fromTheme("list-remove") - decrease_font_size_button = ColorButton(QColor('smoke')) - decrease_font_size_button.setIcon(icon) - decrease_font_size_button.pressed.connect( - lambda: self.decrease_font_size_clicked(font_size_input) + def load_preferences(self): + "Load the last saved preferences from settings" + settings = QSettings() + LATENCY_COLOR_TYPE.current_color = QColor( + settings.value( + f"scheduler/preferences/{LATENCY_COLOR_TYPE.name}", + defaultValue=LATENCY_COLOR_TYPE.DEFAULT, + type=str, + ) ) - decrease_font_size_button.setShortcut( - QCoreApplication.translate("MainWindow", "Ctrl+-") + LATENCY_COLOR_TYPE.changed = settings.value( + f"scheduler/preferences/{LATENCY_COLOR_TYPE.name}_changed", False, bool ) - - hlayout2.addWidget(decrease_font_size_button) - - groupbox2.setLayout(hlayout2) - hlayout.addWidget(groupbox2) - - groupbox.setLayout(hlayout) - layout.addWidget(groupbox) - - reset_font_button = ColorButton(QColor('silver')) - reset_font_button.setText('Reset All Font Settings') - reset_font_button.pressed.connect( - lambda: self.reset_font_clicked(font_size_input, italic_button, bold_button) + self._converted_color_per_type = settings.value( + f"scheduler/preferences/{LATENCY_COLOR_TYPE.name}/per_type", + self._converted_color_per_type, ) - layout.addWidget(reset_font_button) - - label = QLabel("") - layout.addWidget(label) - - reset_all_button = ColorButton(QColor('salmon')) - reset_all_button.setText('Reset all settings') - reset_all_button.pressed.connect( - lambda: self.reset_all_clicked(font_size_input, italic_button, bold_button) + self._color_changed_per_type = settings.value( + f"scheduler/preferences/{LATENCY_COLOR_TYPE.name}/per_type_changed", + False, + bool, ) - layout.addWidget(reset_all_button) - - dialog.setLayout(layout) - button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Close) - button_box.ButtonLayout(QDialogButtonBox.MacLayout) - button_box.accepted.connect(dialog.accept) - button_box.rejected.connect(dialog.close) - layout.addWidget(button_box) - - dialog.exec_() - - def create_color_button(self, color: ColorDataType) -> ColorButton: - """Create a colored button to be used to modify a certain color + settings.sync() - Parameters - ---------- - color : ColorDataType - The ColorDataType assigned to the butten to be created. - """ - button = ColorButton(color.DEFAULT) - button.setText(color.name) - if color.name == "Latency color": - button.pressed.connect( - lambda: self.set_latency_color_by_type_name(all=True) - ) - elif color.name == "Latency color per type": - button.pressed.connect( - lambda: self.set_latency_color_by_type_name(all=False) + for key, color_str in self._converted_color_per_type.items(): + color = QColor(color_str) + self._color_per_type[key] = color + Match = ( + (color == LATENCY_COLOR_TYPE.current_color) + if LATENCY_COLOR_TYPE.changed + else (color == LATENCY_COLOR_TYPE.DEFAULT) ) - else: - button.pressed.connect(lambda: self.color_button_clicked(color)) - return button - - def set_latency_color_by_type_name(self, all: bool) -> None: - """ - Set latency color based on operation type names. - - Parameters - ---------- - all : bool - Indicates if the color of all type names to be modified. - """ - if LATENCY_COLOR_TYPE.changed: - current_color = LATENCY_COLOR_TYPE.current_color - else: - current_color = LATENCY_COLOR_TYPE.DEFAULT + if self._color_changed_per_type and not Match: + self._changed_operation_colors[key] = color + self.update_color_preferences() - # Prompt user to select operation type if not setting color for all types - if not all: - used_types = self._schedule.get_used_type_names() - operation_type, ok = QInputDialog.getItem( - self, "Select operation type", "Type", used_types, editable=False - ) + if FONT.changed: + FONT.current_font.setPointSizeF(FONT.size) + FONT.current_font.setItalic(FONT.italic) + FONT.current_font.setBold(FONT.bold) + self._graph._font_change(FONT.current_font) + self._graph._font_color_change(FONT.color) else: - operation_type = "all operations" - ok = False - - # Open a color dialog to get the selected color - if all or ok: - color = QColorDialog.getColor( - current_color, self, f"Select the color of {operation_type}" - ) + self._graph._font_change(FONT.DEFAULT) + self._graph._font_color_change(FONT.DEFAULT_COLOR) - # If a valid color is selected, update color settings and graph - if color.isValid(): - if all: - LATENCY_COLOR_TYPE.changed = True - self._color_changed_per_type = False - self._changed_operation_colors.clear() - LATENCY_COLOR_TYPE.current_color = color - # Save color settings for each operation type - else: - self._color_changed_per_type = True - self._changed_operation_colors[operation_type] = color - self.update_color_preferences() - self.update_statusbar("Preferences updated") + self.update_statusbar("Saved Preferences Loaded") def update_color_preferences(self) -> None: """Update preferences of Latency color per type""" + used_type_names = self._schedule.get_used_type_names() match (LATENCY_COLOR_TYPE.changed, self._color_changed_per_type): case (True, False): - for type_name in self._schedule.get_used_type_names(): + for type_name in used_type_names: self._color_per_type[type_name] = LATENCY_COLOR_TYPE.current_color case (False, False): - for type_name in self._schedule.get_used_type_names(): + for type_name in used_type_names: self._color_per_type[type_name] = LATENCY_COLOR_TYPE.DEFAULT case (False, True): - for type_name in self._schedule.get_used_type_names(): + for type_name in used_type_names: if type_name in self._changed_operation_colors: self._color_per_type[type_name] = ( self._changed_operation_colors[type_name] @@ -1159,7 +964,7 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): else: self._color_per_type[type_name] = LATENCY_COLOR_TYPE.DEFAULT case (True, True): - for type_name in self._schedule.get_used_type_names(): + for type_name in used_type_names: if type_name in self._changed_operation_colors: self._color_per_type[type_name] = ( self._changed_operation_colors[type_name] @@ -1174,7 +979,8 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): """Save preferences of Latency color per type in settings""" settings = QSettings() for key, color in self._color_per_type.items(): - self._graph._color_change(color, key) + if self._graph: + self._graph._color_change(color, key) self._converted_color_per_type[key] = color.name() settings.setValue( f"scheduler/preferences/{LATENCY_COLOR_TYPE.name}", @@ -1193,274 +999,6 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): self._color_changed_per_type, ) - def color_button_clicked(self, color_type: ColorDataType) -> None: - """ - Open a color dialog to select a color based on the specified color type - - Parameters - ---------- - color_type : ColorDataType - The ColorDataType to be changed. - """ - settings = QSettings() - if color_type.changed: - current_color = color_type.current_color - else: - current_color = color_type.DEFAULT - - color = QColorDialog.getColor(current_color, self, f"Select {color_type.name}") - # If a valid color is selected, update the current color and settings - if color.isValid(): - color_type.current_color = color - color_type.changed = color_type.current_color != color_type.DEFAULT - settings.setValue(f"scheduler/preferences/{color_type.name}", color.name()) - settings.sync() - - self._graph._signals.reopen.emit() - self.update_statusbar("Preferences Updated") - - def font_clicked( - self, line: QLineEdit, italicbutton: ColorButton, boldbutton: ColorButton - ) -> None: - """ - Open a font dialog to select a font and update the current font. - - Parameters - ---------- - line : QLineEdit - The line displaying the text size to be matched with the chosen font. - italicbutton : ColorButton - The button displaying the italic state to be matched with the chosen font. - boldbutton : ColorButton - The button displaying the bold state to be matched with the chosen font. - """ - current_font = FONT.current_font if FONT.changed else FONT.DEFAULT - - (font, ok) = QFontDialog.getFont(current_font, self) - if ok: - FONT.current_font = font - FONT.size = int(font.pointSizeF()) - FONT.bold = font.bold() - FONT.italic = font.italic() - self.update_font() - self.match_dialog_font(line, italicbutton, boldbutton) - self.update_statusbar("Preferences Updated") - - def update_font(self): - """Update font preferences based on current Font settings""" - settings = QSettings() - FONT.changed = not ( - FONT.current_font == FONT.DEFAULT - and FONT.size == int(FONT.DEFAULT.pointSizeF()) - and FONT.italic == FONT.DEFAULT.italic() - and FONT.bold == FONT.DEFAULT.bold() - ) - settings.setValue("scheduler/preferences/font", FONT.current_font.toString()) - settings.setValue("scheduler/preferences/font_size", FONT.size) - settings.setValue("scheduler/preferences/font_bold", FONT.bold) - settings.setValue("scheduler/preferences/font_italic", FONT.italic) - settings.sync() - self.load_preferences() - - def load_preferences(self): - "Load the last saved preferences from settings" - settings = QSettings() - LATENCY_COLOR_TYPE.current_color = QColor( - settings.value( - f"scheduler/preferences/{LATENCY_COLOR_TYPE.name}", - defaultValue=LATENCY_COLOR_TYPE.DEFAULT, - type=str, - ) - ) - LATENCY_COLOR_TYPE.changed = settings.value( - f"scheduler/preferences/{LATENCY_COLOR_TYPE.name}_changed", False, bool - ) - self._converted_color_per_type = settings.value( - f"scheduler/preferences/{LATENCY_COLOR_TYPE.name}/per_type", - self._converted_color_per_type, - ) - self._color_changed_per_type = settings.value( - f"scheduler/preferences/{LATENCY_COLOR_TYPE.name}/per_type_changed", - False, - bool, - ) - settings.sync() - - for key, color_str in self._converted_color_per_type.items(): - color = QColor(color_str) - self._color_per_type[key] = color - Match = ( - (color == LATENCY_COLOR_TYPE.current_color) - if LATENCY_COLOR_TYPE.changed - else (color == LATENCY_COLOR_TYPE.DEFAULT) - ) - if self._color_changed_per_type and not Match: - self._changed_operation_colors[key] = color - self.update_color_preferences() - - if FONT.changed: - FONT.current_font.setPointSizeF(FONT.size) - FONT.current_font.setItalic(FONT.italic) - FONT.current_font.setBold(FONT.bold) - self._graph._font_change(FONT.current_font) - self._graph._font_color_change(FONT.color) - else: - self._graph._font_change(FONT.DEFAULT) - self._graph._font_color_change(FONT.DEFAULT_COLOR) - - self.update_statusbar("Saved Preferences Loaded") - - def font_color_clicked(self): - """Select a font color and update preferences""" - settings = QSettings() - color = QColorDialog.getColor(FONT.color, self, "Select font color") - if color.isValid(): - FONT.color = color - FONT.changed = True - settings.setValue("scheduler/preferences/font_color", FONT.color.name()) - settings.sync() - self._graph._font_color_change(FONT.color) - - def set_font_size_clicked(self, size): - """Set the font size to the specified size and update the font - size - The font size to be set. - """ - FONT.size = int(size) if size != "" else 6 - FONT.current_font.setPointSizeF(FONT.size) - self.update_font() - - def italic_font_clicked(self, button: ColorButton): - """Toggle the font style to italic if not already italic, otherwise remove italic - button: ColorButton - The clicked button. Used to indicate state on/off. - """ - FONT.italic = not FONT.italic - FONT.current_font.setItalic(FONT.italic) - ( - button.set_color(QColor('silver')) - if FONT.italic - else button.set_color(QColor('snow')) - ) - self.update_font() - - def bold_font_clicked(self, button: ColorButton): - """ - Toggle the font style to bold if not already bold, otherwise unbold. - - Parameters - ---------- - button : ColorButton - The clicked button. Used to indicate state on/off. - """ - FONT.bold = not FONT.bold - FONT.current_font.setBold(FONT.bold) - FONT.current_font.setWeight(50) - ( - button.set_color(QColor('silver')) - if FONT.bold - else button.set_color(QColor('snow')) - ) - self.update_font() - - def increase_font_size_clicked(self, line: QLineEdit) -> None: - """ - Increase the font size by 1. - - Parameters - ---------- - line : QLineEdit - The line displaying the text size to be matched. - """ - if FONT.size <= 71: - line.setText(str(FONT.size + 1)) - else: - line.setText(str(FONT.size)) - - def decrease_font_size_clicked(self, line: QLineEdit) -> None: - """ - Decrease the font size by 1. - - Parameters - ---------- - line : QLineEdit - The line displaying the text size to be matched. - """ - if FONT.size >= 7: - line.setText(str(FONT.size - 1)) - else: - line.setText(str(FONT.size)) - - def reset_color_clicked(self): - """Reset the color settings""" - settings = QSettings() - reset_color_settings(settings) - self._color_changed_per_type = False - self.update_color_preferences() - - self._graph._color_change(LATENCY_COLOR_TYPE.DEFAULT, "all operations") - self._graph._signals.reopen.emit() - self.load_preferences() - - def reset_font_clicked( - self, line: QLineEdit, italicbutton: ColorButton, boldbutton: ColorButton - ): - """ - Reset the font settings. - - Parameters - ---------- - line : QLineEdit - The line displaying the text size to be matched with the chosen font. - italicbutton : ColorButton - The button displaying the italic state to be matched with the chosen font. - boldbutton : ColorButton - The button displaying the bold state to be matched with the chosen font. - """ - FONT.current_font = QFont("Times", 12) - FONT.changed = False - FONT.color = FONT.DEFAULT_COLOR - FONT.size = int(FONT.DEFAULT.pointSizeF()) - FONT.bold = FONT.DEFAULT.bold() - FONT.italic = FONT.DEFAULT.italic() - self.update_font() - self.load_preferences() - self.match_dialog_font(line, italicbutton, boldbutton) - - def reset_all_clicked( - self, line: QLineEdit, italicbutton: ColorButton, boldbutton: ColorButton - ): - """Reset both the color and the font settings.""" - self.reset_color_clicked() - self.reset_font_clicked(line, italicbutton, boldbutton) - - def match_dialog_font( - self, line: QLineEdit, italicbutton: ColorButton, boldbutton: ColorButton - ) -> None: - """ - Update the widgets on the preference dialog to match the current font. - - Parameters - ---------- - line : QLineEdit - The line displaying the text size to be matched with the current font. - italicbutton : ColorButton - The button displaying the italic state to be matched with the current font. - boldbutton : ColorButton - The button displaying the bold state to be matched with the current font. - """ - line.setText(str(FONT.size)) - - if FONT.italic: - italicbutton.set_color(QColor('silver')) - else: - italicbutton.set_color(QColor('snow')) - - if FONT.bold: - boldbutton.set_color(QColor('silver')) - else: - boldbutton.set_color(QColor('snow')) - @Slot(str) def _show_execution_times_for_type(self, type_name): self._execution_time_plot(type_name) @@ -1557,7 +1095,7 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): self._recent_files_actions[i].setVisible(False) def _open_recent_file(self, action): - if action.data().filePath().endswith('.bsc'): + if action.data().filePath().endswith(".bsc"): self._open_schedule_file(action.data().filePath()) else: self._load_from_file(action.data().filePath()) @@ -1602,10 +1140,10 @@ class ScheduleMainWindow(QMainWindow, Ui_MainWindow): """Callback for toggling full screen mode.""" if self.isFullScreen(): self.showNormal() - self.actionToggle_full_screen.setIcon(get_icon('full-screen')) + self.actionToggle_full_screen.setIcon(get_icon("full-screen")) else: self.showFullScreen() - self.actionToggle_full_screen.setIcon(get_icon('full-screen-exit')) + self.actionToggle_full_screen.setIcon(get_icon("full-screen-exit")) @overload diff --git a/b_asic/scheduler_gui/operation_item.py b/b_asic/scheduler_gui/operation_item.py index 347ea9ee..63778af3 100644 --- a/b_asic/scheduler_gui/operation_item.py +++ b/b_asic/scheduler_gui/operation_item.py @@ -5,6 +5,7 @@ B-ASIC Scheduler-GUI Operation Item Module. Contains the scheduler_gui OperationItem class for drawing and maintain an operation in the schedule. """ + from typing import TYPE_CHECKING, cast # QGraphics and QPainter imports @@ -347,16 +348,16 @@ class OperationItem(QGraphicsItemGroup): def _open_context_menu(self): menu = QMenu() - swap = QAction(get_icon('swap'), "Swap") + swap = QAction(get_icon("swap"), "Swap") menu.addAction(swap) swap.setEnabled(self._operation.is_swappable) swap.triggered.connect(self._swap_io) slacks = self._parent.schedule.slacks(self._operation.graph_id) - asap = QAction(get_icon('asap'), "Move as soon as possible") + asap = QAction(get_icon("asap"), "Move as soon as possible") asap.triggered.connect(self._move_asap) asap.setEnabled(slacks[0] > 0) menu.addAction(asap) - alap = QAction(get_icon('alap'), "Move as late as possible") + alap = QAction(get_icon("alap"), "Move as late as possible") alap.triggered.connect(self._move_alap) alap.setEnabled(slacks[1] > 0) menu.addAction(alap) diff --git a/b_asic/scheduler_gui/preferences_dialog.py b/b_asic/scheduler_gui/preferences_dialog.py new file mode 100644 index 00000000..6ca990ca --- /dev/null +++ b/b_asic/scheduler_gui/preferences_dialog.py @@ -0,0 +1,422 @@ +# QGraphics and QPainter imports +from qtpy.QtCore import ( + QCoreApplication, + QSettings, + Qt, +) +from qtpy.QtGui import QColor, QFont, QIcon, QIntValidator +from qtpy.QtWidgets import ( + QColorDialog, + QDialogButtonBox, + QFontDialog, + QGroupBox, + QHBoxLayout, + QInputDialog, + QLabel, + QLineEdit, + QVBoxLayout, + QWidget, +) + +from b_asic.gui_utils.color_button import ColorButton +from b_asic.scheduler_gui._preferences import ( + ACTIVE_COLOR_TYPE, + EXECUTION_TIME_COLOR_TYPE, + FONT, + LATENCY_COLOR_TYPE, + SIGNAL_COLOR_TYPE, + SIGNAL_WARNING_COLOR_TYPE, + ColorDataType, + reset_color_settings, +) + + +class PreferencesDialog(QWidget): + def __init__(self, parent): + super().__init__() + self._parent = parent + self.setWindowTitle("Preferences") + layout = QVBoxLayout() + layout.setSpacing(15) + + # Add label for the dialog + label = QLabel("Customize fonts and colors") + layout.addWidget(label) + + groupbox = QGroupBox() + hlayout = QHBoxLayout() + label = QLabel("Color settings:") + layout.addWidget(label) + hlayout.setSpacing(20) + + hlayout.addWidget(self.create_color_button(EXECUTION_TIME_COLOR_TYPE)) + hlayout.addWidget(self.create_color_button(LATENCY_COLOR_TYPE)) + hlayout.addWidget( + self.create_color_button( + ColorDataType( + current_color=LATENCY_COLOR_TYPE.DEFAULT, + DEFAULT=QColor("skyblue"), + name="Latency color per type", + ) + ) + ) + + groupbox.setLayout(hlayout) + layout.addWidget(groupbox) + + label = QLabel("Signal colors:") + layout.addWidget(label) + groupbox = QGroupBox() + hlayout = QHBoxLayout() + hlayout.setSpacing(20) + + signal_color_button = self.create_color_button(SIGNAL_COLOR_TYPE) + signal_color_button.setStyleSheet( + f"color: {QColor(255, 255, 255, 0).name()}; background-color: {SIGNAL_COLOR_TYPE.DEFAULT.name()}" + ) + + hlayout.addWidget(signal_color_button) + hlayout.addWidget(self.create_color_button(SIGNAL_WARNING_COLOR_TYPE)) + hlayout.addWidget(self.create_color_button(ACTIVE_COLOR_TYPE)) + + groupbox.setLayout(hlayout) + layout.addWidget(groupbox) + + reset_color_button = ColorButton(QColor("silver")) + reset_color_button.setText("Reset all color settings") + reset_color_button.pressed.connect(self.reset_color_clicked) + layout.addWidget(reset_color_button) + + label = QLabel("Font settings:") + layout.addWidget(label) + + groupbox = QGroupBox() + hlayout = QHBoxLayout() + hlayout.setSpacing(10) + + font_button = ColorButton(QColor("moccasin")) + font_button.setText("Font settings") + hlayout.addWidget(font_button) + + font_color_button = ColorButton(QColor("moccasin")) + font_color_button.setText("Font color") + font_color_button.pressed.connect(self.font_color_clicked) + hlayout.addWidget(font_color_button) + + groupbox2 = QGroupBox() + hlayout2 = QHBoxLayout() + + icon = QIcon.fromTheme("format-text-italic") + self._italic_button = ( + ColorButton(QColor("silver")) + if FONT.italic + else ColorButton(QColor("snow")) + ) + self._italic_button.setIcon(icon) + self._italic_button.pressed.connect(lambda: self.italic_font_clicked()) + hlayout2.addWidget(self._italic_button) + + icon = QIcon.fromTheme("format-text-bold") + self._bold_button = ColorButton( + QColor("silver") if FONT.bold else QColor("snow") + ) + self._bold_button.setIcon(icon) + self._bold_button.pressed.connect(lambda: self.bold_font_clicked()) + hlayout2.addWidget(self._bold_button) + + groupbox2.setLayout(hlayout2) + hlayout.addWidget(groupbox2) + + groupbox2 = QGroupBox() + hlayout2 = QHBoxLayout() + self._font_size_input = QLineEdit() + font_button.pressed.connect(lambda: self.font_clicked()) + + icon = QIcon.fromTheme("list-add") + increase_font_size_button = ColorButton(QColor("smoke")) + increase_font_size_button.setIcon(icon) + increase_font_size_button.pressed.connect( + lambda: self.increase_font_size_clicked() + ) + increase_font_size_button.setShortcut( + QCoreApplication.translate("MainWindow", "Ctrl++") + ) + hlayout2.addWidget(increase_font_size_button) + + self._font_size_input.setPlaceholderText("Font Size") + self._font_size_input.setText(f"Font Size: {FONT.size}") + self._font_size_input.setValidator(QIntValidator(0, 99)) + self._font_size_input.setAlignment(Qt.AlignCenter) + self._font_size_input.textChanged.connect(lambda: self.set_font_size_clicked()) + hlayout2.addWidget(self._font_size_input) + + icon = QIcon.fromTheme("list-remove") + decrease_font_size_button = ColorButton(QColor("smoke")) + decrease_font_size_button.setIcon(icon) + decrease_font_size_button.pressed.connect( + lambda: self.decrease_font_size_clicked() + ) + decrease_font_size_button.setShortcut( + QCoreApplication.translate("MainWindow", "Ctrl+-") + ) + + hlayout2.addWidget(decrease_font_size_button) + + groupbox2.setLayout(hlayout2) + hlayout.addWidget(groupbox2) + + groupbox.setLayout(hlayout) + layout.addWidget(groupbox) + + reset_font_button = ColorButton(QColor("silver")) + reset_font_button.setText("Reset All Font Settings") + reset_font_button.pressed.connect(lambda: self.reset_font_clicked()) + layout.addWidget(reset_font_button) + + label = QLabel("") + layout.addWidget(label) + + reset_all_button = ColorButton(QColor("salmon")) + reset_all_button.setText("Reset all settings") + reset_all_button.pressed.connect(lambda: self.reset_all_clicked()) + layout.addWidget(reset_all_button) + + self.setLayout(layout) + button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Close) + button_box.ButtonLayout(QDialogButtonBox.MacLayout) + button_box.accepted.connect(self.close) + button_box.rejected.connect(self.close) + layout.addWidget(button_box) + + def increase_font_size_clicked(self) -> None: + """ + Increase the font size by 1. + """ + if FONT.size <= 71: + self._font_size_input.setText(str(FONT.size + 1)) + else: + self._font_size_input.setText(str(FONT.size)) + + def decrease_font_size_clicked(self) -> None: + """ + Decrease the font size by 1. + """ + if FONT.size >= 7: + self._font_size_input.setText(str(FONT.size - 1)) + else: + self._font_size_input.setText(str(FONT.size)) + + def set_font_size_clicked(self): + """ + Set the font size to the specified size and update the font. + """ + size = self._font_size_input.text() + FONT.size = int(size) if size != "" else 6 + FONT.current_font.setPointSizeF(FONT.size) + self.update_font() + + def create_color_button(self, color: ColorDataType) -> ColorButton: + """Create a colored button to be used to modify a certain color + + Parameters + ---------- + color : ColorDataType + The ColorDataType assigned to the butten to be created. + """ + button = ColorButton(color.DEFAULT) + button.setText(color.name) + if color.name == "Latency color": + button.pressed.connect( + lambda: self.set_latency_color_by_type_name(all=True) + ) + elif color.name == "Latency color per type": + button.pressed.connect( + lambda: self.set_latency_color_by_type_name(all=False) + ) + else: + button.pressed.connect(lambda: self.color_button_clicked(color)) + return button + + def italic_font_clicked(self): + """ + Toggle the font style to italic if not already italic, otherwise remove italic. + """ + FONT.italic = not FONT.italic + FONT.current_font.setItalic(FONT.italic) + if FONT.italic: + self._italic_button.set_color(QColor("silver")) + else: + self._italic_button.set_color(QColor("snow")) + self.update_font() + + def bold_font_clicked(self): + """ + Toggle the font style to bold if not already bold, otherwise unbold. + """ + FONT.bold = not FONT.bold + FONT.current_font.setBold(FONT.bold) + FONT.current_font.setWeight(50) + if FONT.bold: + self._bold_button.set_color(QColor("silver")) + else: + self._bold_button.set_color(QColor("snow")) + self.update_font() + + def set_latency_color_by_type_name(self, all: bool) -> None: + """ + Set latency color based on operation type names. + + Parameters + ---------- + all : bool + Indicates if the color of all type names to be modified. + """ + if LATENCY_COLOR_TYPE.changed: + current_color = LATENCY_COLOR_TYPE.current_color + else: + current_color = LATENCY_COLOR_TYPE.DEFAULT + + # Prompt user to select operation type if not setting color for all types + if not all: + used_types = self._parent._schedule.get_used_type_names() + operation_type, ok = QInputDialog.getItem( + self, "Select operation type", "Type", used_types, editable=False + ) + else: + operation_type = "all operations" + ok = False + + # Open a color dialog to get the selected color + if all or ok: + color = QColorDialog.getColor( + current_color, self, f"Select the color of {operation_type}" + ) + + # If a valid color is selected, update color settings and graph + if color.isValid(): + if all: + LATENCY_COLOR_TYPE.changed = True + self._parent._color_changed_per_type = False + self._parent._changed_operation_colors.clear() + LATENCY_COLOR_TYPE.current_color = color + # Save color settings for each operation type + else: + self._parent._color_changed_per_type = True + self._parent._changed_operation_colors[operation_type] = color + self._parent.update_color_preferences() + self._parent.update_statusbar("Preferences updated") + + def color_button_clicked(self, color_type: ColorDataType) -> None: + """ + Open a color dialog to select a color based on the specified color type + + Parameters + ---------- + color_type : ColorDataType + The ColorDataType to be changed. + """ + settings = QSettings() + if color_type.changed: + current_color = color_type.current_color + else: + current_color = color_type.DEFAULT + + color = QColorDialog.getColor(current_color, self, f"Select {color_type.name}") + # If a valid color is selected, update the current color and settings + if color.isValid(): + color_type.current_color = color + color_type.changed = color_type.current_color != color_type.DEFAULT + settings.setValue(f"scheduler/preferences/{color_type.name}", color.name()) + settings.sync() + + self._parent._graph._signals.reopen.emit() + self._parent.update_statusbar("Preferences Updated") + + def font_clicked(self) -> None: + """ + Open a font dialog to select a font and update the current font. + """ + current_font = FONT.current_font if FONT.changed else FONT.DEFAULT + + (font, ok) = QFontDialog.getFont(current_font, self) + if ok: + FONT.current_font = font + FONT.size = int(font.pointSizeF()) + FONT.bold = font.bold() + FONT.italic = font.italic() + self.update_font() + self.match_dialog_font() + self._parent.update_statusbar("Preferences Updated") + + def reset_font_clicked(self): + """ + Reset the font settings. + """ + FONT.current_font = QFont("Times", 12) + FONT.changed = False + FONT.color = FONT.DEFAULT_COLOR + FONT.size = int(FONT.DEFAULT.pointSizeF()) + FONT.bold = FONT.DEFAULT.bold() + FONT.italic = FONT.DEFAULT.italic() + self.update_font() + self._parent.load_preferences() + self.match_dialog_font() + + def match_dialog_font(self) -> None: + """ + Update the widgets on the preference dialog to match the current font. + """ + self._font_size_input.setText(str(FONT.size)) + + if FONT.italic: + self._italicbutton.set_color(QColor("silver")) + else: + self._italicbutton.set_color(QColor("snow")) + + if FONT.bold: + self._boldbutton.set_color(QColor("silver")) + else: + self._boldbutton.set_color(QColor("snow")) + + def update_font(self): + """Update font preferences based on current Font settings""" + settings = QSettings() + FONT.changed = not ( + FONT.current_font == FONT.DEFAULT + and FONT.size == int(FONT.DEFAULT.pointSizeF()) + and FONT.italic == FONT.DEFAULT.italic() + and FONT.bold == FONT.DEFAULT.bold() + ) + settings.setValue("scheduler/preferences/font", FONT.current_font.toString()) + settings.setValue("scheduler/preferences/font_size", FONT.size) + settings.setValue("scheduler/preferences/font_bold", FONT.bold) + settings.setValue("scheduler/preferences/font_italic", FONT.italic) + settings.sync() + self._parent.load_preferences() + + def font_color_clicked(self): + """Select a font color and update preferences""" + settings = QSettings() + color = QColorDialog.getColor(FONT.color, self, "Select font color") + if color.isValid(): + FONT.color = color + FONT.changed = True + settings.setValue("scheduler/preferences/font_color", FONT.color.name()) + settings.sync() + self._parent._graph._font_color_change(FONT.color) + + def reset_color_clicked(self): + """Reset the color settings""" + settings = QSettings() + reset_color_settings(settings) + self._parent._color_changed_per_type = False + self._parent.update_color_preferences() + + self._parent._graph._color_change(LATENCY_COLOR_TYPE.DEFAULT, "all operations") + self._parent._graph._signals.reopen.emit() + self._parent.load_preferences() + + def reset_all_clicked(self): + """Reset both the color and the font settings.""" + self.reset_color_clicked() + self.reset_font_clicked() diff --git a/b_asic/scheduler_gui/scheduler_event.py b/b_asic/scheduler_gui/scheduler_event.py index b191fa50..0524d176 100644 --- a/b_asic/scheduler_gui/scheduler_event.py +++ b/b_asic/scheduler_gui/scheduler_event.py @@ -5,6 +5,7 @@ B-ASIC Scheduler-GUI Graphics Scheduler Event Module. Contains the scheduler_ui SchedulerEvent class containing event filters and handlers for SchedulerItem objects. """ + import math from typing import overload diff --git a/b_asic/scheduler_gui/scheduler_item.py b/b_asic/scheduler_gui/scheduler_item.py index 72f9cf82..43091545 100644 --- a/b_asic/scheduler_gui/scheduler_item.py +++ b/b_asic/scheduler_gui/scheduler_item.py @@ -5,6 +5,7 @@ B-ASIC Scheduler-GUI Scheduler Item Module. Contains the scheduler_gui SchedulerItem class for drawing and maintaining a schedule. """ + from collections import defaultdict from math import floor from pprint import pprint diff --git a/b_asic/scheduler_gui/timeline_item.py b/b_asic/scheduler_gui/timeline_item.py index 5fd10d07..fc29669f 100644 --- a/b_asic/scheduler_gui/timeline_item.py +++ b/b_asic/scheduler_gui/timeline_item.py @@ -5,6 +5,7 @@ B-ASIC Scheduler-GUI Timeline Item Module. Contains the scheduler_gui TimelineItem class for drawing and maintain the timeline in a schedule. """ + from typing import overload # QGraphics and QPainter imports diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py index 72c4a316..de2a9db8 100644 --- a/b_asic/signal_flow_graph.py +++ b/b_asic/signal_flow_graph.py @@ -671,12 +671,12 @@ class SFG(AbstractOperation): signal.remove_source() signal.set_source(component.output(index_out)) - if component_copy.type_name() == 'out': + if component_copy.type_name() == "out": sfg_copy._output_operations.remove(component_copy) warnings.warn( f"Output port {component_copy.graph_id} has been removed", stacklevel=2 ) - if component.type_name() == 'out': + if component.type_name() == "out": sfg_copy._output_operations.append(component) return sfg_copy() # Copy again to update IDs. @@ -1019,14 +1019,14 @@ class SFG(AbstractOperation): if port.operation.output_count > 1: sub.node( port_string, - shape='rectangle', + shape="rectangle", height="0.1", width="0.1", ) else: sub.node( port_string, - shape='rectangle', + shape="rectangle", label=port.operation.graph_id, height="0.1", width="0.1", @@ -1688,7 +1688,7 @@ class SFG(AbstractOperation): and source_name not in branch_nodes ): branch_nodes.add(source_name) - dg.node(source_name, shape='point') + dg.node(source_name, shape="point") taillabel = ( str(source.index) if source.operation.output_count > 1 and port_numbering @@ -1697,7 +1697,7 @@ class SFG(AbstractOperation): dg.edge( source.operation.graph_id, source_name, - arrowhead='none', + arrowhead="none", taillabel=taillabel, ) else: @@ -1857,7 +1857,7 @@ class SFG(AbstractOperation): time_of_loop = 0 number_of_t_in_loop = 0 for element in loop: - if ''.join([i for i in element if not i.isdigit()]) == 't': + if "".join([i for i in element if not i.isdigit()]) == "t": number_of_t_in_loop += 1 for key, item in op_and_latency.items(): if key in element: @@ -1881,7 +1881,7 @@ class SFG(AbstractOperation): """ inputs_used = [] for used_input in self._used_ids: - if 'in' in str(used_input): + if "in" in str(used_input): used_input = used_input.replace("in", "") inputs_used.append(int(used_input)) if inputs_used == []: @@ -1937,7 +1937,7 @@ class SFG(AbstractOperation): """ delay_element_used = [] for delay_element in self._used_ids: - if ''.join([i for i in delay_element if not i.isdigit()]) == 't': + if "".join([i for i in delay_element if not i.isdigit()]) == "t": delay_element_used.append(delay_element) delay_element_used.sort() input_index_used = [] @@ -1945,10 +1945,10 @@ class SFG(AbstractOperation): output_index_used = [] outputs_used = [] for used_inout in self._used_ids: - if 'in' in str(used_inout): + if "in" in str(used_inout): inputs_used.append(used_inout) input_index_used.append(int(used_inout.replace("in", ""))) - elif 'out' in str(used_inout): + elif "out" in str(used_inout): outputs_used.append(used_inout) output_index_used.append(int(used_inout.replace("out", ""))) if input_index_used == []: @@ -1995,12 +1995,12 @@ class SFG(AbstractOperation): raise ValueError("Empty SFG") addition_with_constant = {} for key, item in dict_of_sfg.items(): - if ''.join([i for i in key if not i.isdigit()]) == 'c': + if "".join([i for i in key if not i.isdigit()]) == "c": addition_with_constant[item[0]] = self.find_by_id(key).value cycles = [ [node] + path for node in dict_of_sfg - if node[0] == 't' + if node[0] == "t" for path in self._dfs(dict_of_sfg, node, node) ] delay_loop_list = [] @@ -2009,7 +2009,7 @@ class SFG(AbstractOperation): temp_list = [] for element in lista: temp_list.append(element) - if element[0] == 't' and len(temp_list) >= 2: + if element[0] == "t" and len(temp_list) >= 2: delay_loop_list.append(temp_list) temp_list = [element] state_space_lista = [] @@ -2024,34 +2024,34 @@ class SFG(AbstractOperation): matrix_in = [0] * mat_col matrix_answer = [0] * mat_row for in_signal in inputs_used: - matrix_in[len(delay_element_used) + int(in_signal.replace('in', ''))] = ( - in_signal.replace('in', 'x') + matrix_in[len(delay_element_used) + int(in_signal.replace("in", ""))] = ( + in_signal.replace("in", "x") ) for delay_element in delay_element_used: matrix_answer[delay_element_used.index(delay_element)] = ( - delay_element.replace('t', 'v') + delay_element.replace("t", "v") ) matrix_in[delay_element_used.index(delay_element)] = ( - delay_element.replace('t', 'v') + delay_element.replace("t", "v") ) paths = self.find_all_paths(dict_of_sfg, in_signal, delay_element) for lista in paths: temp_list = [] for element in lista: temp_list.append(element) - if element[0] == 't': + if element[0] == "t": state_space_lista.append(temp_list) temp_list = [element] for out_signal in outputs_used: paths = self.find_all_paths(dict_of_sfg, in_signal, out_signal) matrix_answer[ - len(delay_element_used) + int(out_signal.replace('out', '')) - ] = out_signal.replace('out', 'y') + len(delay_element_used) + int(out_signal.replace("out", "")) + ] = out_signal.replace("out", "y") for lista in paths: temp_list1 = [] for element in lista: temp_list1.append(element) - if element[0] == 't': + if element[0] == "t": state_space_lista.append(temp_list1) temp_list1 = [element] if "out" in element: @@ -2064,7 +2064,7 @@ class SFG(AbstractOperation): if x not in state_space_list_no_dup ] for lista in state_space_list_no_dup: - if "in" in lista[0] and lista[-1][0] == 't': + if "in" in lista[0] and lista[-1][0] == "t": row = int(lista[-1].replace("t", "")) column = len(delay_element_used) + int(lista[0].replace("in", "")) temp_value = 1 @@ -2086,7 +2086,7 @@ class SFG(AbstractOperation): if key == element: temp_value += int(value) mat_content[row, column] += temp_value - elif lista[0][0] == 't' and lista[-1][0] == 't': + elif lista[0][0] == "t" and lista[-1][0] == "t": row = int(lista[-1].replace("t", "")) column = int(lista[0].replace("t", "")) temp_value = 1 @@ -2097,7 +2097,7 @@ class SFG(AbstractOperation): if key == element: temp_value += int(value) mat_content[row, column] += temp_value - elif lista[0][0] == 't' and "out" in lista[-1]: + elif lista[0][0] == "t" and "out" in lista[-1]: row = len(delay_element_used) + int(lista[-1].replace("out", "")) column = int(lista[0].replace("t", "")) temp_value = 1 @@ -2206,9 +2206,9 @@ class SFG(AbstractOperation): # Add suffixes to all graphIDs and names in order to keep them separated for i in range(factor): for operation in sfgs[i].operations: - suffix = f'_{i}' + suffix = f"_{i}" operation.graph_id = operation.graph_id + suffix - if operation.name[:7] not in ['', 'input_t', 'output_']: + if operation.name[:7] not in ["", "input_t", "output_"]: operation.name = operation.name + suffix input_name_to_idx = {} # save the input port indices for future reference @@ -2248,7 +2248,7 @@ class SFG(AbstractOperation): input_port.connect(port) delay_placements[port] = [i, number_of_delays_between] sfgs[i].graph_id = ( - f'sfg{i}' # deterministically set the graphID of the sfgs + f"sfg{i}" # deterministically set the graphID of the sfgs ) sfg = SFG(new_inputs, new_outputs) # create a new SFG to remove floating nodes @@ -2257,11 +2257,11 @@ class SFG(AbstractOperation): for port, val in delay_placements.items(): i, no_of_delays = val for _ in range(no_of_delays): - sfg = sfg.insert_operation_after(f'sfg{i}.{port.index}', Delay()) + sfg = sfg.insert_operation_after(f"sfg{i}.{port.index}", Delay()) # Flatten all the copies of the original SFG for i in range(factor): - sfg.find_by_id(f'sfg{i}').connect_external_signals_to_components() + sfg.find_by_id(f"sfg{i}").connect_external_signals_to_components() sfg = sfg() return sfg diff --git a/docs_sphinx/conf.py b/docs_sphinx/conf.py index 32ba2028..1822a3ec 100644 --- a/docs_sphinx/conf.py +++ b/docs_sphinx/conf.py @@ -8,40 +8,40 @@ import shutil -project = 'B-ASIC' -copyright = '2020-2025, Oscar Gustafsson et al' -author = 'Oscar Gustafsson et al' +project = "B-ASIC" +copyright = "2020-2025, Oscar Gustafsson et al" +author = "Oscar Gustafsson et al" html_logo = "../logos/logo_tiny.png" -pygments_style = 'sphinx' +pygments_style = "sphinx" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.inheritance_diagram', - 'sphinx.ext.intersphinx', - 'sphinx_gallery.gen_gallery', - 'numpydoc', # Needs to be loaded *after* autodoc. - 'sphinx_copybutton', + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.inheritance_diagram", + "sphinx.ext.intersphinx", + "sphinx_gallery.gen_gallery", + "numpydoc", # Needs to be loaded *after* autodoc. + "sphinx_copybutton", ] -templates_path = ['_templates'] -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] autodoc_docstring_signature = True # nitpicky = True intersphinx_mapping = { - 'python': ('https://docs.python.org/3/', None), - 'graphviz': ('https://graphviz.readthedocs.io/en/stable/', None), - 'matplotlib': ('https://matplotlib.org/stable/', None), - 'numpy': ('https://numpy.org/doc/stable/', None), - 'networkx': ('https://networkx.org/documentation/stable', None), - 'mplsignal': ('https://mplsignal.readthedocs.io/en/stable/', None), + "python": ("https://docs.python.org/3/", None), + "graphviz": ("https://graphviz.readthedocs.io/en/stable/", None), + "matplotlib": ("https://matplotlib.org/stable/", None), + "numpy": ("https://numpy.org/doc/stable/", None), + "networkx": ("https://networkx.org/documentation/stable", None), + "mplsignal": ("https://mplsignal.readthedocs.io/en/stable/", None), } numpydoc_show_class_members = False @@ -58,30 +58,30 @@ numpydoc_validation_checks = { inheritance_node_attrs = dict(fontsize=16) -graphviz_dot = shutil.which('dot') +graphviz_dot = shutil.which("dot") -html_favicon = '_static/icon_logo.png' +html_favicon = "_static/icon_logo.png" # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = 'furo' -html_static_path = ['_static'] +html_theme = "furo" +html_static_path = ["_static"] # -- Options for sphinx-gallery -- sphinx_gallery_conf = { - 'examples_dirs': '../examples', # path to your example scripts - 'gallery_dirs': 'examples', # path to where to save gallery generated output - 'plot_gallery': 'True', # sphinx-gallery/913 - 'filename_pattern': '.', - 'doc_module': ('b_asic',), - 'reference_url': {'b_asic': None}, - 'image_scrapers': ( + "examples_dirs": "../examples", # path to your example scripts + "gallery_dirs": "examples", # path to where to save gallery generated output + "plot_gallery": "True", # sphinx-gallery/913 + "filename_pattern": ".", + "doc_module": ("b_asic",), + "reference_url": {"b_asic": None}, + "image_scrapers": ( # qtgallery.qtscraper, - 'matplotlib', + "matplotlib", ), - 'reset_modules': ( + "reset_modules": ( # qtgallery.reset_qapp, - 'matplotlib', + "matplotlib", ), } diff --git a/docs_sphinx/scheduler_gui.rst b/docs_sphinx/scheduler_gui.rst index 288bf0a5..03ead85c 100644 --- a/docs_sphinx/scheduler_gui.rst +++ b/docs_sphinx/scheduler_gui.rst @@ -37,6 +37,14 @@ scheduler\_gui.operation\_item module :undoc-members: :show-inheritance: +scheduler\_gui.preferences\_dialog module +------------------------------------- + +.. automodule:: b_asic.scheduler_gui.preferences_dialog + :members: + :undoc-members: + :show-inheritance: + scheduler\_gui.scheduler\_event module -------------------------------------- diff --git a/test/fixtures/schedule.py b/test/fixtures/schedule.py index e844ed92..5d654e19 100644 --- a/test/fixtures/schedule.py +++ b/test/fixtures/schedule.py @@ -47,14 +47,14 @@ def schedule_direct_form_iir_lp_filter(sfg_direct_form_iir_lp_filter: SFG): schedule = Schedule( sfg_direct_form_iir_lp_filter, scheduler=ASAPScheduler(), cyclic=True ) - schedule.move_operation('cmul3', -1) - schedule.move_operation('cmul2', -1) - schedule.move_operation('cmul3', -10) - schedule.move_operation('cmul3', 1) - schedule.move_operation('cmul2', -8) - schedule.move_operation('add3', 1) - schedule.move_operation('add3', 1) - schedule.move_operation('cmul1', 1) - schedule.move_operation('cmul1', 1) - schedule.move_operation('cmul3', 2) + schedule.move_operation("cmul3", -1) + schedule.move_operation("cmul2", -1) + schedule.move_operation("cmul3", -10) + schedule.move_operation("cmul3", 1) + schedule.move_operation("cmul2", -8) + schedule.move_operation("add3", 1) + schedule.move_operation("add3", 1) + schedule.move_operation("cmul1", 1) + schedule.move_operation("cmul1", 1) + schedule.move_operation("cmul3", 2) return schedule diff --git a/test/fixtures/signal_flow_graph.py b/test/fixtures/signal_flow_graph.py index 7eaccbe7..df3e50b0 100644 --- a/test/fixtures/signal_flow_graph.py +++ b/test/fixtures/signal_flow_graph.py @@ -288,13 +288,13 @@ def sfg_two_tap_fir(): # Operations: t1 = Delay(initial_value=0, name="t1") cmul1 = ConstantMultiplication( - value=0.5, name="cmul1", latency_offsets={'in0': None, 'out0': None} + value=0.5, name="cmul1", latency_offsets={"in0": None, "out0": None} ) add1 = Addition( - name="add1", latency_offsets={'in0': None, 'in1': None, 'out0': None} + name="add1", latency_offsets={"in0": None, "in1": None, "out0": None} ) cmul2 = ConstantMultiplication( - value=0.5, name="cmul2", latency_offsets={'in0': None, 'out0': None} + value=0.5, name="cmul2", latency_offsets={"in0": None, "out0": None} ) # Signals: @@ -305,7 +305,7 @@ def sfg_two_tap_fir(): Signal(source=cmul1.output(0), destination=add1.input(0)) Signal(source=add1.output(0), destination=out1.input(0)) Signal(source=cmul2.output(0), destination=add1.input(1)) - return SFG(inputs=[in1], outputs=[out1], name='twotapfir') + return SFG(inputs=[in1], outputs=[out1], name="twotapfir") @pytest.fixture @@ -331,7 +331,7 @@ def sfg_direct_form_iir_lp_filter(): d0.input(0).connect(top_node) d1.input(0).connect(d0) y <<= a1 * d0 + a2 * d1 + a0 * top_node - return SFG(inputs=[x], outputs=[y], name='Direct Form 2 IIR Lowpass filter') + return SFG(inputs=[x], outputs=[y], name="Direct Form 2 IIR Lowpass filter") @pytest.fixture diff --git a/test/integration/test_sfg_to_architecture.py b/test/integration/test_sfg_to_architecture.py index 6eebe152..be645aed 100644 --- a/test/integration/test_sfg_to_architecture.py +++ b/test/integration/test_sfg_to_architecture.py @@ -120,8 +120,8 @@ def test_pe_and_memory_constrained_chedule(): 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') + 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() diff --git a/test/unit/test_architecture.py b/test/unit/test_architecture.py index b0721419..6ceabe63 100644 --- a/test/unit/test_architecture.py +++ b/test/unit/test_architecture.py @@ -96,8 +96,8 @@ def test_architecture(schedule_direct_form_iir_lp_filter: Schedule): output_pe, ] s = ( - 'digraph {\n\tnode [shape=box]\n\t' - + 'adder' + "digraph {\n\tnode [shape=box]\n\t" + + "adder" + ' [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="4">' + '<TR><TD COLSPAN="1" PORT="in0">in0</TD>' + '<TD COLSPAN="1" PORT="in1">in1</TD></TR>' @@ -105,7 +105,7 @@ def test_architecture(schedule_direct_form_iir_lp_filter: Schedule): + '<TR><TD COLSPAN="2" PORT="out0">out0</TD></TR>' + '</TABLE>> fillcolor="#00B9E7" fontname="Times New Roman" style=filled]\n}' ) - assert adder._digraph().source in (s, s + '\n') + assert adder._digraph().source in (s, s + "\n") # Extract zero-length memory variables direct_conn, mvs = mvs.split_on_length() @@ -119,16 +119,16 @@ def test_architecture(schedule_direct_form_iir_lp_filter: Schedule): for i, memory in enumerate(memories): memory.set_entity_name(f"MEM{i}") s = ( - 'digraph {\n\tnode [shape=box]\n\tMEM0' + "digraph {\n\tnode [shape=box]\n\tMEM0" + ' [label=<<TABLE BORDER="0" CELLBORDER="1"' + ' CELLSPACING="0" CELLPADDING="4">' + '<TR><TD COLSPAN="1" PORT="in0">in0</TD></TR>' + '<TR><TD COLSPAN="1"><B>MEM0</B></TD></TR>' + '<TR><TD COLSPAN="1" PORT="out0">out0</TD></TR>' + '</TABLE>> fillcolor="#00CFB5" fontname="Times New Roman" ' - + 'style=filled]\n}' + + "style=filled]\n}" ) - assert memory._digraph().source in (s, s + '\n') + assert memory._digraph().source in (s, s + "\n") assert memory.schedule_time == 18 # Smoke test memory.show_content() @@ -141,7 +141,7 @@ def test_architecture(schedule_direct_form_iir_lp_filter: Schedule): memory.show_content() # Set invalid name - with pytest.raises(ValueError, match='32 is not a valid VHDL identifier'): + with pytest.raises(ValueError, match="32 is not a valid VHDL identifier"): adder.set_entity_name("32") assert adder.entity_name == "adder" assert repr(adder) == "adder" @@ -158,22 +158,22 @@ def test_architecture(schedule_direct_form_iir_lp_filter: Schedule): # Graph representation # Parts are non-deterministic, but this first part seems OK - s = 'digraph {\n\tnode [shape=box]\n\tsplines=spline\n\tsubgraph cluster_memories' + s = "digraph {\n\tnode [shape=box]\n\tsplines=spline\n\tsubgraph cluster_memories" assert architecture._digraph().source.startswith(s) - s = 'digraph {\n\tnode [shape=box]\n\tsplines=spline\n\tMEM0' + s = "digraph {\n\tnode [shape=box]\n\tsplines=spline\n\tMEM0" assert architecture._digraph(cluster=False).source.startswith(s) assert architecture.schedule_time == 18 for pe in processing_elements: assert pe.schedule_time == 18 - assert architecture.resource_from_name('adder') == adder + assert architecture.resource_from_name("adder") == adder - assert architecture.get_interconnects_for_memory('MEM0') == ( + assert architecture.get_interconnects_for_memory("MEM0") == ( {adder: 2, multiplier: 2, input_pe: 1}, {adder: 4, multiplier: 4}, ) - assert architecture.get_interconnects_for_pe('adder') == ( + assert architecture.get_interconnects_for_pe("adder") == ( [ {(multiplier, 0): 2, (memory, 0): 1, (adder, 0): 1}, {(memory, 0): 3, (multiplier, 0): 1}, @@ -199,13 +199,13 @@ def test_move_process(schedule_direct_form_iir_lp_filter: Schedule): # Create necessary processing elements processing_elements: List[ProcessingElement] = [ - ProcessingElement(operation, entity_name=f'pe{i}') + ProcessingElement(operation, entity_name=f"pe{i}") for i, operation in enumerate(chain(adders1, adders2, const_mults)) ] for i, pc in enumerate(inputs): - processing_elements.append(ProcessingElement(pc, entity_name=f'input{i}')) + processing_elements.append(ProcessingElement(pc, entity_name=f"input{i}")) for i, pc in enumerate(outputs): - processing_elements.append(ProcessingElement(pc, entity_name=f'output{i}')) + processing_elements.append(ProcessingElement(pc, entity_name=f"output{i}")) # Extract zero-length memory variables direct_conn, mvs = mvs.split_on_length() @@ -219,31 +219,31 @@ def test_move_process(schedule_direct_form_iir_lp_filter: Schedule): ) # Some movement that must work - assert memories[1].collection.from_name('cmul3.0') - architecture.move_process('cmul3.0', memories[1], memories[0]) - assert memories[0].collection.from_name('cmul3.0') + assert memories[1].collection.from_name("cmul3.0") + architecture.move_process("cmul3.0", memories[1], memories[0]) + assert memories[0].collection.from_name("cmul3.0") - assert memories[1].collection.from_name('in0.0') - architecture.move_process('in0.0', memories[1], memories[0]) - assert memories[0].collection.from_name('in0.0') + assert memories[1].collection.from_name("in0.0") + architecture.move_process("in0.0", memories[1], memories[0]) + assert memories[0].collection.from_name("in0.0") - assert processing_elements[0].collection.from_name('add0') - architecture.move_process('add0', processing_elements[0], processing_elements[1]) - assert processing_elements[1].collection.from_name('add0') + assert processing_elements[0].collection.from_name("add0") + architecture.move_process("add0", processing_elements[0], processing_elements[1]) + assert processing_elements[1].collection.from_name("add0") # Processes leave the resources they have moved from with pytest.raises(KeyError): - memories[1].collection.from_name('cmul3.0') + memories[1].collection.from_name("cmul3.0") with pytest.raises(KeyError): - memories[1].collection.from_name('in0.0') + memories[1].collection.from_name("in0.0") with pytest.raises(KeyError): - processing_elements[0].collection.from_name('add0') + processing_elements[0].collection.from_name("add0") # Processes can only be moved when the source and destination process-types match with pytest.raises(TypeError, match="cmul3.0 not of type"): - architecture.move_process('cmul3.0', memories[0], processing_elements[0]) + architecture.move_process("cmul3.0", memories[0], processing_elements[0]) with pytest.raises(KeyError, match="invalid_name not in"): - architecture.move_process('invalid_name', memories[0], processing_elements[1]) + architecture.move_process("invalid_name", memories[0], processing_elements[1]) def test_resource_errors(precedence_sfg_delays): @@ -260,7 +260,7 @@ def test_resource_errors(precedence_sfg_delays): operations = schedule.get_operations() additions = operations.get_by_type_name(Addition.type_name()) with pytest.raises( - ValueError, match='Cannot map ProcessCollection to single ProcessingElement' + ValueError, match="Cannot map ProcessCollection to single ProcessingElement" ): ProcessingElement(additions) diff --git a/test/unit/test_gui.py b/test/unit/test_gui.py index d910765c..39a31857 100644 --- a/test/unit/test_gui.py +++ b/test/unit/test_gui.py @@ -21,10 +21,10 @@ def test_start(qtbot): def test_load(qtbot, datadir): widget = SFGMainWindow() qtbot.addWidget(widget) - widget._load_from_file(datadir.join('twotapfir.py')) - assert 'twotapfir' in widget._sfg_dict + widget._load_from_file(datadir.join("twotapfir.py")) + assert "twotapfir" in widget._sfg_dict widget._clear_workspace() - assert 'twotapfir' not in widget._sfg_dict + assert "twotapfir" not in widget._sfg_dict assert not widget._sfg_dict widget.exit_app() @@ -33,8 +33,8 @@ def test_load(qtbot, datadir): def test_flip(qtbot, datadir): widget = SFGMainWindow() qtbot.addWidget(widget) - widget._load_from_file(datadir.join('twotapfir.py')) - sfg = widget._sfg_dict['twotapfir'] + widget._load_from_file(datadir.join("twotapfir.py")) + sfg = widget._sfg_dict["twotapfir"] op = sfg.find_by_name("cmul2") dragbutton = widget._drag_buttons[op[0]] assert not dragbutton.is_flipped() @@ -46,8 +46,8 @@ def test_flip(qtbot, datadir): def test_sfg_invalidated_by_remove_of_operation(qtbot, datadir): widget = SFGMainWindow() qtbot.addWidget(widget) - widget._load_from_file(datadir.join('twotapfir.py')) - sfg = widget._sfg_dict['twotapfir'] + widget._load_from_file(datadir.join("twotapfir.py")) + sfg = widget._sfg_dict["twotapfir"] ops_before_remove = len(widget._drag_buttons) op = sfg.find_by_name("cmul2") dragbutton = widget._drag_buttons[op[0]] @@ -61,8 +61,8 @@ def test_sfg_invalidated_by_remove_of_operation(qtbot, datadir): def test_sfg_invalidated_by_deleting_of_operation(qtbot, datadir): widget = SFGMainWindow() qtbot.addWidget(widget) - widget._load_from_file(datadir.join('twotapfir.py')) - sfg = widget._sfg_dict['twotapfir'] + widget._load_from_file(datadir.join("twotapfir.py")) + sfg = widget._sfg_dict["twotapfir"] ops_before_remove = len(widget._drag_buttons) op = sfg.find_by_name("cmul2") dragbutton = widget._drag_buttons[op[0]] @@ -78,8 +78,8 @@ def test_sfg_invalidated_by_deleting_of_operation(qtbot, datadir): def test_select_operation(qtbot, datadir): widget = SFGMainWindow() qtbot.addWidget(widget) - widget._load_from_file(datadir.join('twotapfir.py')) - sfg = widget._sfg_dict['twotapfir'] + widget._load_from_file(datadir.join("twotapfir.py")) + sfg = widget._sfg_dict["twotapfir"] op = sfg.find_by_name("cmul2")[0] dragbutton = widget._drag_buttons[op] assert not dragbutton.pressed @@ -170,8 +170,8 @@ def test_properties_window_smoke_test(qtbot, datadir): # Should really check that the contents are correct and changes works etc widget = SFGMainWindow() qtbot.addWidget(widget) - widget._load_from_file(datadir.join('twotapfir.py')) - sfg = widget._sfg_dict['twotapfir'] + widget._load_from_file(datadir.join("twotapfir.py")) + sfg = widget._sfg_dict["twotapfir"] op = sfg.find_by_name("cmul2")[0] dragbutton = widget._drag_buttons[op] dragbutton.show_properties_window() @@ -187,8 +187,8 @@ def test_properties_window_change_name(qtbot, datadir): # Should really check that the contents are correct and changes works etc widget = SFGMainWindow() qtbot.addWidget(widget) - widget._load_from_file(datadir.join('twotapfir.py')) - sfg = widget._sfg_dict['twotapfir'] + widget._load_from_file(datadir.join("twotapfir.py")) + sfg = widget._sfg_dict["twotapfir"] op = sfg.find_by_name("cmul2")[0] dragbutton = widget._drag_buttons[op] assert dragbutton.name == "cmul2" diff --git a/test/unit/test_gui/twotapfir.py b/test/unit/test_gui/twotapfir.py index 44300bb8..120675aa 100644 --- a/test/unit/test_gui/twotapfir.py +++ b/test/unit/test_gui/twotapfir.py @@ -15,11 +15,11 @@ out1 = Output(name="") # Operations: t1 = Delay(initial_value=0, name="") cmul1 = ConstantMultiplication( - value=-0.5, name="cmul1", latency_offsets={'in0': None, 'out0': None} + 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} + value=0.5, name="cmul2", latency_offsets={"in0": None, "out0": None} ) # Signals: @@ -30,15 +30,15 @@ Signal(source=in1.output(0), destination=cmul2.input(0)) Signal(source=cmul1.output(0), destination=add1.input(0)) Signal(source=add1.output(0), destination=out1.input(0)) Signal(source=cmul2.output(0), destination=add1.input(1)) -twotapfir = SFG(inputs=[in1], outputs=[out1], name='twotapfir') +twotapfir = SFG(inputs=[in1], outputs=[out1], name="twotapfir") # SFG Properties: -prop = {'name': twotapfir} +prop = {"name": twotapfir} positions = { - 't1': (-209, 19), - 'cmul1': (-95, 76), - 'add1': (0, 95), - 'cmul2': (-209, 114), - 'out1': (76, 95), - 'in1': (-323, 19), + "t1": (-209, 19), + "cmul1": (-95, 76), + "add1": (0, 95), + "cmul2": (-209, 114), + "out1": (76, 95), + "in1": (-323, 19), } diff --git a/test/unit/test_list_schedulers.py b/test/unit/test_list_schedulers.py index 076c2d05..52d29a13 100644 --- a/test/unit/test_list_schedulers.py +++ b/test/unit/test_list_schedulers.py @@ -1921,8 +1921,8 @@ def _validate_recreated_sfg_filter(sfg: SFG, schedule: Schedule) -> None: sim2 = Simulation(schedule.sfg, [Impulse()]) sim2.run_for(1024) - spectrum_1 = abs(np.fft.fft(sim1.results['0'])) - spectrum_2 = abs(np.fft.fft(sim2.results['0'])) + spectrum_1 = abs(np.fft.fft(sim1.results["0"])) + spectrum_2 = abs(np.fft.fft(sim2.results["0"])) assert np.allclose(spectrum_1, spectrum_2) diff --git a/test/unit/test_process.py b/test/unit/test_process.py index 92a4eed1..6b11e5e5 100644 --- a/test/unit/test_process.py +++ b/test/unit/test_process.py @@ -14,7 +14,7 @@ def test_PlainMemoryVariable(): assert mem.read_ports == [4, 5] assert repr(mem) == "PlainMemoryVariable(3, 0, {4: 1, 5: 2}, 'Var. 0')" - mem2 = PlainMemoryVariable(2, 0, {4: 2, 5: 3}, 'foo') + mem2 = PlainMemoryVariable(2, 0, {4: 2, 5: 3}, "foo") assert repr(mem2) == "PlainMemoryVariable(2, 0, {4: 2, 5: 3}, 'foo')" assert mem2 < mem @@ -30,7 +30,7 @@ def test_MemoryVariables(secondorder_iir_schedule): "MemoryVariable\\(3, <b_asic.port.OutputPort object at 0x[a-fA-F0-9]+>," " {<b_asic.port.InputPort object at 0x[a-fA-F0-9]+>: 4}, 'cmul0.0'\\)" ) - mem_var = [m for m in mem_vars if m.name == 'cmul0.0'][0] + mem_var = [m for m in mem_vars if m.name == "cmul0.0"][0] assert pattern.match(repr(mem_var)) assert mem_var.execution_time == 4 assert mem_var.start_time == 3 @@ -44,7 +44,7 @@ def test_OperatorProcess_error(secondorder_iir_schedule): def test_MultiReadProcess(): mv = PlainMemoryVariable(3, 0, {0: 1, 1: 2, 2: 5}, name="MV") - with pytest.raises(KeyError, match=r'Process MV: 3 not in life_times: \[1, 2, 5\]'): + with pytest.raises(KeyError, match=r"Process MV: 3 not in life_times: \[1, 2, 5\]"): mv._remove_life_time(3) assert mv.life_times == [1, 2, 5] diff --git a/test/unit/test_resources.py b/test/unit/test_resources.py index a8aafc3a..43d84d84 100644 --- a/test/unit/test_resources.py +++ b/test/unit/test_resources.py @@ -15,7 +15,7 @@ from b_asic.resources import ProcessCollection, _ForwardBackwardTable class TestProcessCollectionPlainMemoryVariable: @matplotlib.testing.decorators.image_comparison( - ['test_draw_process_collection.png'] + ["test_draw_process_collection.png"] ) def test_draw_process_collection(self, simple_collection): fig, ax = plt.subplots() @@ -23,7 +23,7 @@ class TestProcessCollectionPlainMemoryVariable: return fig @matplotlib.testing.decorators.image_comparison( - ['test_draw_matrix_transposer_4.png'] + ["test_draw_matrix_transposer_4.png"] ) def test_draw_matrix_transposer_4(self): fig, ax = plt.subplots() @@ -72,7 +72,7 @@ class TestProcessCollectionPlainMemoryVariable: assert len(split) == 2 @matplotlib.testing.decorators.image_comparison( - ['test_left_edge_cell_assignment.png'] + ["test_left_edge_cell_assignment.png"] ) def test_left_edge_cell_assignment(self, simple_collection: ProcessCollection): fig, ax = plt.subplots(1, 2) @@ -86,7 +86,7 @@ class TestProcessCollectionPlainMemoryVariable: collection = generate_matrix_transposer(4, min_lifetime=5) assignment_left_edge = collection._left_edge_assignment() assignment_graph_color = collection.split_on_execution_time( - heuristic="graph_color", coloring_strategy='saturation_largest_first' + heuristic="graph_color", coloring_strategy="saturation_largest_first" ) assert len(assignment_left_edge) == 18 assert len(assignment_graph_color) == 16 @@ -111,10 +111,10 @@ class TestProcessCollectionPlainMemoryVariable: assignment = collection.split_on_execution_time(heuristic="graph_color") collection.generate_memory_based_storage_vhdl( filename=( - 'b_asic/codegen/testbench/' - f'streaming_matrix_transposition_memory_{rows}x{cols}.vhdl' + "b_asic/codegen/testbench/" + f"streaming_matrix_transposition_memory_{rows}x{cols}.vhdl" ), - entity_name=f'streaming_matrix_transposition_memory_{rows}x{cols}', + entity_name=f"streaming_matrix_transposition_memory_{rows}x{cols}", assignment=assignment, word_length=16, adr_mux_size=mux_size, @@ -127,10 +127,10 @@ class TestProcessCollectionPlainMemoryVariable: rows, min_lifetime=0 ).generate_register_based_storage_vhdl( filename=( - 'b_asic/codegen/testbench/streaming_matrix_transposition_' - f'register_{rows}x{rows}.vhdl' + "b_asic/codegen/testbench/streaming_matrix_transposition_" + f"register_{rows}x{rows}.vhdl" ), - entity_name=f'streaming_matrix_transposition_register_{rows}x{rows}', + entity_name=f"streaming_matrix_transposition_register_{rows}x{rows}", word_length=16, ) @@ -138,10 +138,10 @@ class TestProcessCollectionPlainMemoryVariable: collection = generate_matrix_transposer(rows=4, cols=8, min_lifetime=2) collection.generate_register_based_storage_vhdl( filename=( - 'b_asic/codegen/testbench/streaming_matrix_transposition_register_' - '4x8.vhdl' + "b_asic/codegen/testbench/streaming_matrix_transposition_register_" + "4x8.vhdl" ), - entity_name='streaming_matrix_transposition_register_4x8', + entity_name="streaming_matrix_transposition_register_4x8", word_length=16, ) @@ -159,14 +159,14 @@ class TestProcessCollectionPlainMemoryVariable: cyclic=True, ) t = _ForwardBackwardTable(collection) - process_names = {match.group(0) for match in re.finditer(r'PC[0-9]+', str(t))} - register_names = {match.group(0) for match in re.finditer(r'R[0-9]+', str(t))} + process_names = {match.group(0) for match in re.finditer(r"PC[0-9]+", str(t))} + register_names = {match.group(0) for match in re.finditer(r"R[0-9]+", str(t))} assert len(process_names) == 6 # 6 process in the collection assert len(register_names) == 5 # 5 register required for i, process in enumerate(sorted(process_names)): - assert process == f'PC{i}' + assert process == f"PC{i}" for i, register in enumerate(sorted(register_names)): - assert register == f'R{i}' + assert register == f"R{i}" def test_generate_random_interleaver(self): return @@ -206,7 +206,7 @@ class TestProcessCollectionPlainMemoryVariable: assert new_proc not in simple_collection @matplotlib.testing.decorators.image_comparison( - ['test_max_min_lifetime_bar_plot.png'] + ["test_max_min_lifetime_bar_plot.png"] ) def test_max_min_lifetime_bar_plot(self): fig, ax = plt.subplots() @@ -229,10 +229,10 @@ class TestProcessCollectionPlainMemoryVariable: def test_multiple_reads_exclusion_greaph(self): # Initial collection - p0 = PlainMemoryVariable(0, 0, {0: 3}, 'P0') - p1 = PlainMemoryVariable(1, 0, {0: 2}, 'P1') - p2 = PlainMemoryVariable(2, 0, {0: 2}, 'P2') - p3 = PlainMemoryVariable(3, 0, {0: 3}, 'P3') + p0 = PlainMemoryVariable(0, 0, {0: 3}, "P0") + p1 = PlainMemoryVariable(1, 0, {0: 2}, "P1") + p2 = PlainMemoryVariable(2, 0, {0: 2}, "P2") + p3 = PlainMemoryVariable(3, 0, {0: 3}, "P3") collection = ProcessCollection({p0, p1, p2, p3}, 5, cyclic=True) exclusion_graph = collection.create_exclusion_graph_from_ports( read_ports=1, @@ -247,7 +247,7 @@ class TestProcessCollectionPlainMemoryVariable: assert exclusion_graph.degree(p3) == 2 # Add multi-read process - p4 = PlainMemoryVariable(0, 0, {0: 1, 1: 2, 2: 3, 3: 4}, 'P4') + p4 = PlainMemoryVariable(0, 0, {0: 1, 1: 2, 2: 3, 3: 4}, "P4") collection.add_process(p4) exclusion_graph = collection.create_exclusion_graph_from_ports( read_ports=1, diff --git a/test/unit/test_schedule.py b/test/unit/test_schedule.py index ac767ff9..b23c76a0 100644 --- a/test/unit/test_schedule.py +++ b/test/unit/test_schedule.py @@ -279,20 +279,20 @@ class TestInit: "out0": 6, } laps = { - 's8': 1, - 's10': 2, - 's15': 1, - 's17': 2, - 's0': 0, - 's3': 0, - 's12': 0, - 's11': 0, - 's14': 0, - 's13': 0, - 's6': 0, - 's4': 0, - 's5': 0, - 's2': 0, + "s8": 1, + "s10": 2, + "s15": 1, + "s17": 2, + "s0": 0, + "s3": 0, + "s12": 0, + "s11": 0, + "s14": 0, + "s13": 0, + "s6": 0, + "s4": 0, + "s5": 0, + "s2": 0, } schedule = Schedule(sfg, start_times=start_times, laps=laps) @@ -526,13 +526,13 @@ class TestRescheduling: ) schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler()) - assert schedule.backward_slack('cmul5') == 16 - assert schedule.forward_slack('cmul5') == 0 - schedule.move_operation_asap('cmul5') - assert schedule.start_time_of_operation('in0') == 0 - assert schedule.laps['cmul5'] == 0 - assert schedule.backward_slack('cmul5') == 0 - assert schedule.forward_slack('cmul5') == 16 + assert schedule.backward_slack("cmul5") == 16 + assert schedule.forward_slack("cmul5") == 0 + schedule.move_operation_asap("cmul5") + assert schedule.start_time_of_operation("in0") == 0 + assert schedule.laps["cmul5"] == 0 + assert schedule.backward_slack("cmul5") == 0 + assert schedule.forward_slack("cmul5") == 16 def test_move_input_asap_does_not_mess_up_laps(self, precedence_sfg_delays): precedence_sfg_delays.set_latency_of_type_name(Addition.type_name(), 1) @@ -541,10 +541,10 @@ class TestRescheduling: ) schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler()) - old_laps = schedule.laps['in0'] - schedule.move_operation_asap('in0') - assert schedule.start_time_of_operation('in0') == 0 - assert schedule.laps['in0'] == old_laps + old_laps = schedule.laps["in0"] + schedule.move_operation_asap("in0") + assert schedule.start_time_of_operation("in0") == 0 + assert schedule.laps["in0"] == old_laps def test_move_operation_acc(self): in0 = Input() @@ -625,8 +625,8 @@ class TestRescheduling: fir_sfg = direct_form_fir( list(range(1, 10)), - mult_properties={'latency': 2, 'execution_time': 1}, - add_properties={'latency': 2, 'execution_time': 1}, + mult_properties={"latency": 2, "execution_time": 1}, + add_properties={"latency": 2, "execution_time": 1}, ) schedule = Schedule(fir_sfg, scheduler=ASAPScheduler()) sfg = schedule.sfg @@ -778,7 +778,7 @@ class TestProcesses: class TestFigureGeneration: @matplotlib.testing.decorators.image_comparison( - ['test__get_figure_no_execution_times.png'], remove_text=True + ["test__get_figure_no_execution_times.png"], remove_text=True ) def test__get_figure_no_execution_times(self, secondorder_iir_schedule): return secondorder_iir_schedule._get_figure() @@ -864,16 +864,16 @@ class TestErrors: ConstantMultiplication.type_name(), 2 ) with pytest.raises(ValueError, match="Must provide laps when using 'provided'"): - Schedule(sfg_simple_filter, start_times={'in0': 0}) + Schedule(sfg_simple_filter, start_times={"in0": 0}) class TestGetUsedTypeNames: def test_secondorder_iir_schedule(self, secondorder_iir_schedule): assert secondorder_iir_schedule.get_used_type_names() == [ - 'add', - 'cmul', - 'in', - 'out', + "add", + "cmul", + "in", + "out", ] diff --git a/test/unit/test_scheduler_gui.py b/test/unit/test_scheduler_gui.py index 0df333f9..9d9b716f 100644 --- a/test/unit/test_scheduler_gui.py +++ b/test/unit/test_scheduler_gui.py @@ -26,3 +26,11 @@ def test_load_schedule(qtbot, sfg_simple_filter): schedule = Schedule(sfg_simple_filter, ASAPScheduler()) widget.open(schedule) assert widget.statusbar.currentMessage() == "Schedule loaded successfully" + + +def test_open_preferences_dialog(qtbot): + widget = ScheduleMainWindow() + qtbot.addWidget(widget) + widget.open_preferences_dialog() + widget._preferences_dialog.close() + widget.exit_app() diff --git a/test/unit/test_sfg.py b/test/unit/test_sfg.py index 50f0fb9d..7cd54fc4 100644 --- a/test/unit/test_sfg.py +++ b/test/unit/test_sfg.py @@ -824,8 +824,8 @@ class TestConnectExternalSignalsToComponentsSoloComp: port goes to multiple operations """ sfg1 = wdf_allpass(0.5) - sfg2 = sfg1.replace_operation(sfg1.find_by_id('sym2p0').to_sfg(), 'sym2p0') - sfg2.find_by_id('sfg0').connect_external_signals_to_components() + sfg2 = sfg1.replace_operation(sfg1.find_by_id("sym2p0").to_sfg(), "sym2p0") + sfg2.find_by_id("sfg0").connect_external_signals_to_components() test_sfg = SFG(sfg2.input_operations, sfg2.output_operations) assert sfg1.evaluate(1) == -0.5 assert test_sfg.evaluate(1) == -0.5 @@ -1218,7 +1218,7 @@ class TestPrecedenceGraph: res = ( 'digraph {\n\trankdir=LR\n\tsubgraph cluster_0 {\n\t\tlabel=N0\n\t\t"in0.0"' ' [label=in0 height=0.1 shape=rectangle width=0.1]\n\t\t"t0.0" [label=t0' - ' height=0.1 shape=rectangle width=0.1]\n\t}\n\tsubgraph cluster_1' + " height=0.1 shape=rectangle width=0.1]\n\t}\n\tsubgraph cluster_1" ' {\n\t\tlabel=N1\n\t\t"cmul0.0" [label=cmul0 height=0.1 shape=rectangle' ' width=0.1]\n\t}\n\tsubgraph cluster_2 {\n\t\tlabel=N2\n\t\t"add0.0"' ' [label=add0 height=0.1 shape=rectangle width=0.1]\n\t}\n\t"in0.0" ->' @@ -1471,7 +1471,7 @@ class TestSFGErrors: ValueError, match="Different number of input and output ports of operation with", ): - sfg.remove_operation('add0') + sfg.remove_operation("add0") def test_inputs_required_for_output(self): in1 = Input() @@ -1499,7 +1499,7 @@ class TestInputDuplicationBug: out1.inputs[0].connect(add1) - twotapfir = SFG(inputs=[in1], outputs=[out1], name='twotapfir') + twotapfir = SFG(inputs=[in1], outputs=[out1], name="twotapfir") assert len([op for op in twotapfir.operations if isinstance(op, Input)]) == 1 @@ -1651,7 +1651,7 @@ class TestSwapIOOfOperation: assert ref_values == swap_values def test_single_accumulator(self, sfg_simple_accumulator: SFG): - self.do_test(sfg_simple_accumulator, 'add1') + self.do_test(sfg_simple_accumulator, "add1") class TestInsertComponentAfter: @@ -1696,14 +1696,14 @@ class TestInsertComponentAfter: with pytest.raises( TypeError, match="Only operations with one input and one output" ): - sfg.insert_operation_after('constant4', SymmetricTwoportAdaptor(0.5)) + sfg.insert_operation_after("constant4", SymmetricTwoportAdaptor(0.5)) def test_insert_component_after_unknown_component_error( self, large_operation_tree_names ): sfg = SFG(outputs=[Output(large_operation_tree_names)]) with pytest.raises(ValueError, match="Unknown component:"): - sfg.insert_operation_after('foo', SquareRoot()) + sfg.insert_operation_after("foo", SquareRoot()) class TestInsertComponentBefore: @@ -1748,31 +1748,30 @@ class TestInsertComponentBefore: with pytest.raises( TypeError, match="Only operations with one input and one output" ): - sfg.insert_operation_before('add0', SymmetricTwoportAdaptor(0.5), port=0) + sfg.insert_operation_before("add0", SymmetricTwoportAdaptor(0.5), port=0) def test_insert_component_before_unknown_component_error( self, large_operation_tree_names ): sfg = SFG(outputs=[Output(large_operation_tree_names)]) with pytest.raises(ValueError, match="Unknown component:"): - sfg.insert_operation_before('foo', SquareRoot()) + sfg.insert_operation_before("foo", SquareRoot()) class TestGetUsedTypeNames: def test_single_accumulator(self, sfg_simple_accumulator: SFG): - assert sfg_simple_accumulator.get_used_type_names() == ['add', 'in', 'out', 't'] + assert sfg_simple_accumulator.get_used_type_names() == ["add", "in", "out", "t"] def test_sfg_nested(self, sfg_nested: SFG): - assert sfg_nested.get_used_type_names() == ['in', 'out', 'sfg'] + assert sfg_nested.get_used_type_names() == ["in", "out", "sfg"] def test_large_operation_tree(self, large_operation_tree): sfg = SFG(outputs=[Output(large_operation_tree)]) - assert sfg.get_used_type_names() == ['add', 'c', 'out'] + assert sfg.get_used_type_names() == ["add", "c", "out"] class Test_Keep_GraphIDs: def test_single_accumulator(self): - i = Input() d = Delay() o = Output(d) @@ -1781,23 +1780,23 @@ class Test_Keep_GraphIDs: d.input(0).connect(a) sfg = SFG([i], [o]) - sfg = sfg.insert_operation_before('t0', ConstantMultiplication(8)) - sfg = sfg.insert_operation_after('t0', ConstantMultiplication(8)) - sfg = sfg.insert_operation(ConstantMultiplication(8), 't0') + sfg = sfg.insert_operation_before("t0", ConstantMultiplication(8)) + sfg = sfg.insert_operation_after("t0", ConstantMultiplication(8)) + sfg = sfg.insert_operation(ConstantMultiplication(8), "t0") assert sfg.get_used_graph_ids() == { - 'add0', - 'cmul0', - 'cmul1', - 'cmul2', - 'cmul3', - 'in0', - 'out0', - 't0', + "add0", + "cmul0", + "cmul1", + "cmul2", + "cmul3", + "in0", + "out0", + "t0", } def test_large_operation_tree(self, large_operation_tree): sfg = SFG(outputs=[Output(large_operation_tree)]) - assert sfg.get_used_type_names() == ['add', 'c', 'out'] + assert sfg.get_used_type_names() == ["add", "c", "out"] class TestInsertDelays: @@ -1816,11 +1815,11 @@ class TestInsertDelays: assert len(sfg.find_by_type_name(d_type_name)) == 3 - sfg.find_by_id('out1').input(0).delay(3) + sfg.find_by_id("out1").input(0).delay(3) sfg = sfg() assert len(sfg.find_by_type_name(d_type_name)) == 6 - source1 = sfg.find_by_id('out1').input(0).signals[0].source.operation + source1 = sfg.find_by_id("out1").input(0).signals[0].source.operation source2 = source1.input(0).signals[0].source.operation source3 = source2.input(0).signals[0].source.operation source4 = source3.input(0).signals[0].source.operation @@ -1859,7 +1858,7 @@ class TestResourceLowerBound: precedence_sfg_delays.resource_lower_bound("cmul", -1) def test_accumulator(self, sfg_simple_accumulator): - sfg_simple_accumulator.set_latency_of_type_name('add', 2) + sfg_simple_accumulator.set_latency_of_type_name("add", 2) with pytest.raises( ValueError, @@ -1889,7 +1888,7 @@ class TestResourceLowerBound: class TestIterationPeriodBound: def test_accumulator(self, sfg_simple_accumulator): - sfg_simple_accumulator.set_latency_of_type_name('add', 2) + sfg_simple_accumulator.set_latency_of_type_name("add", 2) assert sfg_simple_accumulator.iteration_period_bound() == 2 def test_no_latency(self, sfg_simple_accumulator): @@ -1900,8 +1899,8 @@ class TestIterationPeriodBound: sfg_simple_accumulator.iteration_period_bound() def test_secondorder_iir(self, precedence_sfg_delays): - precedence_sfg_delays.set_latency_of_type_name('add', 2) - precedence_sfg_delays.set_latency_of_type_name('cmul', 3) + precedence_sfg_delays.set_latency_of_type_name("add", 2) + precedence_sfg_delays.set_latency_of_type_name("cmul", 3) assert precedence_sfg_delays.iteration_period_bound() == 10 def test_fractional_value(self): @@ -1956,25 +1955,25 @@ class TestLoops: class TestStateSpace: def test_accumulator(self, sfg_simple_accumulator): ss = sfg_simple_accumulator.state_space_representation() - assert ss[0] == ['v0', 'y0'] + assert ss[0] == ["v0", "y0"] assert (ss[1] == np.array([[1.0, 1.0], [0.0, 1.0]])).all() - assert ss[2] == ['v0', 'x0'] + 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'] + assert ss[0] == ["v0", "v1", "y0"] mat = np.array([[3.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'] + assert ss[2] == ["v0", "v1", "x0"] # @pytest.mark.xfail() def test_sfg_two_inputs_two_outputs(self, sfg_two_inputs_two_outputs): ss = sfg_two_inputs_two_outputs.state_space_representation() - assert ss[0] == ['y0', 'y1'] + assert ss[0] == ["y0", "y1"] assert (ss[1] == np.array([[1.0, 1.0], [1.0, 2.0]])).all() - assert ss[2] == ['x0', 'x1'] + assert ss[2] == ["x0", "x1"] def test_sfg_two_inputs_two_outputs_independent( self, sfg_two_inputs_two_outputs_independent @@ -1982,9 +1981,9 @@ class TestStateSpace: # assert sfg_two_inputs_two_outputs_independent.state_space_representation() == 1 ss = sfg_two_inputs_two_outputs_independent.state_space_representation() - assert ss[0] == ['y0', 'y1'] + assert ss[0] == ["y0", "y1"] assert (ss[1] == np.array([[1.0, 0.0], [0.0, 4.0]])).all() - assert ss[2] == ['x0', 'x1'] + assert ss[2] == ["x0", "x1"] def test_sfg_two_inputs_two_outputs_independent_with_cmul( self, sfg_two_inputs_two_outputs_independent_with_cmul @@ -1993,6 +1992,6 @@ class TestStateSpace: sfg_two_inputs_two_outputs_independent_with_cmul.state_space_representation() ) - assert ss[0] == ['y0', 'y1'] + assert ss[0] == ["y0", "y1"] assert (ss[1] == np.array([[20.0, 0.0], [0.0, 8.0]])).all() - assert ss[2] == ['x0', 'x1'] + assert ss[2] == ["x0", "x1"] diff --git a/test/unit/test_sfg_generators.py b/test/unit/test_sfg_generators.py index 77397971..a51224bd 100644 --- a/test/unit/test_sfg_generators.py +++ b/test/unit/test_sfg_generators.py @@ -124,20 +124,20 @@ def test_direct_form_fir(): sim = Simulation(sfg, [Impulse()]) sim.run_for(4) impulse_response.append(0.0) - assert np.allclose(sim.results['0'], impulse_response) + assert np.allclose(sim.results["0"], impulse_response) impulse_response = [0.3, 0.4, 0.5, 0.6, 0.3] sfg = direct_form_fir( (0.3, 0.4, 0.5, 0.6, 0.3), - mult_properties={'latency': 2, 'execution_time': 1}, - add_properties={'latency': 1, 'execution_time': 1}, + mult_properties={"latency": 2, "execution_time": 1}, + add_properties={"latency": 1, "execution_time": 1}, ) assert sfg.critical_path_time() == 6 sim = Simulation(sfg, [Impulse()]) sim.run_for(6) impulse_response.append(0.0) - assert np.allclose(sim.results['0'], impulse_response) + assert np.allclose(sim.results["0"], impulse_response) impulse_response = [0.3] sfg = direct_form_fir(impulse_response) @@ -189,20 +189,20 @@ def test_transposed_direct_form_fir(): sim = Simulation(sfg, [Impulse()]) sim.run_for(4) impulse_response.append(0.0) - assert np.allclose(sim.results['0'], impulse_response) + assert np.allclose(sim.results["0"], impulse_response) impulse_response = [0.3, 0.4, 0.5, 0.6, 0.3] sfg = transposed_direct_form_fir( (0.3, 0.4, 0.5, 0.6, 0.3), - mult_properties={'latency': 2, 'execution_time': 1}, - add_properties={'latency': 1, 'execution_time': 1}, + mult_properties={"latency": 2, "execution_time": 1}, + add_properties={"latency": 1, "execution_time": 1}, ) assert sfg.critical_path_time() == 3 sim = Simulation(sfg, [Impulse()]) sim.run_for(6) impulse_response.append(0.0) - assert np.allclose(sim.results['0'], impulse_response) + assert np.allclose(sim.results["0"], impulse_response) impulse_response = [0.3] sfg = transposed_direct_form_fir(impulse_response) @@ -318,7 +318,7 @@ class TestDirectFormIIRType1: sim = Simulation(sfg, [ZeroPad(input_signal)]) sim.run_for(100) - assert np.allclose(sim.results['0'], reference_filter_output) + assert np.allclose(sim.results["0"], reference_filter_output) def test_random_input_compare_with_scipy_butterworth_filter(self): N = 10 @@ -334,13 +334,13 @@ class TestDirectFormIIRType1: sim = Simulation(sfg, [ZeroPad(input_signal)]) sim.run_for(100) - assert np.allclose(sim.results['0'], reference_filter_output) + assert np.allclose(sim.results["0"], reference_filter_output) def test_random_input_compare_with_scipy_elliptic_filter(self): N = 2 Wc = 0.3 - b, a = signal.ellip(N, 0.1, 60, Wc, btype='low', analog=False) + b, a = signal.ellip(N, 0.1, 60, Wc, btype="low", analog=False) b, a = signal.butter(N, Wc, btype="lowpass", output="ba") input_signal = np.random.randn(100) @@ -351,7 +351,7 @@ class TestDirectFormIIRType1: sim = Simulation(sfg, [ZeroPad(input_signal)]) sim.run_for(100) - assert np.allclose(sim.results['0'], reference_filter_output) + assert np.allclose(sim.results["0"], reference_filter_output) def test_add_and_mult_properties(self): N = 17 @@ -438,7 +438,7 @@ class TestDirectFormIIRType2: sim = Simulation(sfg, [ZeroPad(input_signal)]) sim.run_for(100) - assert np.allclose(sim.results['0'], reference_filter_output) + assert np.allclose(sim.results["0"], reference_filter_output) def test_random_input_compare_with_scipy_butterworth_filter(self): N = 10 @@ -454,13 +454,13 @@ class TestDirectFormIIRType2: sim = Simulation(sfg, [ZeroPad(input_signal)]) sim.run_for(100) - assert np.allclose(sim.results['0'], reference_filter_output) + assert np.allclose(sim.results["0"], reference_filter_output) def test_random_input_compare_with_scipy_elliptic_filter(self): N = 2 Wc = 0.3 - b, a = signal.ellip(N, 0.1, 60, Wc, btype='low', analog=False) + b, a = signal.ellip(N, 0.1, 60, Wc, btype="low", analog=False) b, a = signal.butter(N, Wc, btype="lowpass", output="ba") input_signal = np.random.randn(100) @@ -471,7 +471,7 @@ class TestDirectFormIIRType2: sim = Simulation(sfg, [ZeroPad(input_signal)]) sim.run_for(100) - assert np.allclose(sim.results['0'], reference_filter_output) + assert np.allclose(sim.results["0"], reference_filter_output) def test_add_and_mult_properties(self): N = 17 diff --git a/test/unit/test_signal_generator.py b/test/unit/test_signal_generator.py index 65266a25..719bb714 100644 --- a/test/unit/test_signal_generator.py +++ b/test/unit/test_signal_generator.py @@ -275,17 +275,17 @@ def test_division(): def test_fromfile(datadir): - g = FromFile(datadir.join('input.csv')) + g = FromFile(datadir.join("input.csv")) assert g(-1) == 0.0 assert g(0) == 0 assert g(1) == 1 assert g(2) == 0 with pytest.raises(FileNotFoundError, match="tset.py not found"): - g = FromFile(datadir.join('tset.py')) + g = FromFile(datadir.join("tset.py")) with pytest.raises(ValueError, match="could not convert string"): - g = FromFile(datadir.join('bad.csv')) + g = FromFile(datadir.join("bad.csv")) def test_upsample(): diff --git a/test/unit/test_simulation_gui.py b/test/unit/test_simulation_gui.py index 177fb11f..4779ecf1 100644 --- a/test/unit/test_simulation_gui.py +++ b/test/unit/test_simulation_gui.py @@ -15,12 +15,12 @@ def test_start(qtbot): def test_start_with_data(qtbot): sim_res = { - '0': [0.5, 0.5, 0, 0], - 'add1': [0.5, 0.5, 0, 0], - 'cmul1': [0, 0.5, 0, 0], - 'cmul2': [0.5, 0, 0, 0], - 'in1': [1, 0, 0, 0], - 't1': [0, 1, 0, 0], + "0": [0.5, 0.5, 0, 0], + "add1": [0.5, 0.5, 0, 0], + "cmul1": [0, 0.5, 0, 0], + "cmul2": [0.5, 0, 0, 0], + "in1": [1, 0, 0, 0], + "t1": [0, 1, 0, 0], } widget = PlotWindow(sim_res) qtbot.addWidget(widget) @@ -32,12 +32,12 @@ def test_start_with_data(qtbot): @pytest.mark.filterwarnings("ignore:No artists with labels found to put in legend") def test_click_buttons(qtbot): sim_res = { - '0': [0.5, 0.5, 0, 0], - 'add1': [0.5, 0.5, 0, 0], - 'cmul1': [0, 0.5, 0, 0], - 'cmul2': [0.5, 0, 0, 0], - 'in1': [1, 0, 0, 0], - 't1': [0, 1, 0, 0], + "0": [0.5, 0.5, 0, 0], + "add1": [0.5, 0.5, 0, 0], + "cmul1": [0, 0.5, 0, 0], + "cmul2": [0.5, 0, 0, 0], + "in1": [1, 0, 0, 0], + "t1": [0, 1, 0, 0], } widget = PlotWindow(sim_res) qtbot.addWidget(widget) -- GitLab