├── src ├── codegen │ ├── __init__.py │ ├── exceptions.py │ ├── list_separation_check.py │ ├── hdl_generation_config.py │ └── hdl_generation_architecture.py ├── dialogs │ ├── __init__.py │ ├── color_changer.py │ └── regex_dialog.py ├── widgets │ ├── __init__.py │ └── OptionMenu.py ├── config.py ├── project.py ├── project_manager.py ├── grid_drawing.py ├── move_handling_canvas_item.py ├── vector_handling.py ├── global_actions_handling.py ├── canvas_modify_bindings.py ├── move_handling_canvas_window.py ├── connector_handling.py ├── state_actions_default.py ├── global_actions_combinatorial.py ├── update_hdl_tab.py ├── linting.py ├── link_dictionary.py ├── write_data_creator.py ├── reset_entry_handling.py ├── state_action_handling.py ├── state_comment.py ├── compile_handling.py ├── move_handling.py └── global_actions.py ├── tests ├── __init__.py ├── test_output │ ├── regression2_e.vhd │ ├── regression1_e.vhd │ ├── multiply_control_e.vhd │ ├── square_root_control_e.vhd │ ├── cordic_square_root_control_e.vhd │ ├── count10_e.vhd │ ├── fifo_control_e.vhd │ ├── uart_receive_e.vhd │ ├── uart_send_e.vhd │ ├── division_unsigned_control.v │ ├── square_root_control_fsm.vhd │ ├── cordic_square_root_control_fsm.vhd │ ├── multiply_wt_booth_control.vhd │ ├── count10_fsm.vhd │ ├── multiply_control_fsm.vhd │ ├── uart_send_fsm.vhd │ ├── fifo_control_fsm.vhd │ ├── regression2_fsm.vhd │ ├── uart_receive_fsm.vhd │ └── regression1_fsm.vhd ├── conftest.py ├── README.md ├── test_golden_file_generation.py └── test_input │ └── fifo_test_error.hfe ├── rsc ├── hfe_icon.ico └── hfe_icon.svg ├── .gitignore ├── .git-blame-ignore-revs ├── .pre-commit-config.yaml ├── .vscode ├── settings.json └── launch.json ├── README.md ├── pyproject.toml ├── LICENSE └── docs └── release_script_README.md /src/codegen/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/dialogs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Tests package for HDL-FSM-Editor 2 | -------------------------------------------------------------------------------- /rsc/hfe_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthiasschweikart/HDL-FSM-Editor/HEAD/rsc/hfe_icon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .vscode/ 3 | .ruff_cache/ 4 | .pytest_cache/ 5 | .mypy_cache/ 6 | .venv/ 7 | 8 | # Build artifacts 9 | releases/ 10 | dist/ 11 | build/ 12 | *.spec 13 | -------------------------------------------------------------------------------- /tests/test_output/regression2_e.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: regression2_e.vhd 2 | -- Created by HDL-FSM-Editor 3 | library ieee; 4 | use ieee.std_logic_1164.all; 5 | 6 | entity regression2 is 7 | port ( 8 | res_i : in std_logic; 9 | clk_i : in std_logic 10 | ); 11 | end entity; 12 | -------------------------------------------------------------------------------- /tests/test_output/regression1_e.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: regression1_e.vhd 2 | -- Created by HDL-FSM-Editor at Wed Aug 13 17:52:13 2025 3 | library ieee; 4 | use ieee.std_logic_1164.all; 5 | 6 | entity regression1 is 7 | port ( 8 | res_i : in std_logic; 9 | clk_i : in std_logic 10 | ); 11 | end entity; 12 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Git SHAs listed in this file are ignored by git blame 2 | 3 | # Move all source files into a src directory 4 | 776e0ed6bf17906e0723cdaa74a2ccc7df7e0270 5 | 6 | # Format all files with ruff 7 | ee3d8f526f839ff5fe846e6976959d057b6c3f96 8 | 9 | # Rename all "local" globals 10 | b192490bc98b3fcdb1065dd046984f70dd502789 11 | -------------------------------------------------------------------------------- /src/dialogs/color_changer.py: -------------------------------------------------------------------------------- 1 | """ 2 | This class lets the user configure a color. 3 | """ 4 | 5 | from tkinter import colorchooser 6 | 7 | 8 | class ColorChanger: 9 | def __init__(self, default_color: str) -> None: 10 | self.default_color = default_color 11 | 12 | def ask_color(self) -> str | None: 13 | return colorchooser.askcolor(self.default_color)[1] 14 | -------------------------------------------------------------------------------- /src/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Configuration constants for HDL-FSM-Editor. 3 | This file contains constants that don't change during runtime. 4 | """ 5 | 6 | # Syntax Highlighting Colors 7 | KEYWORD_COLORS = { 8 | "not_read": "orange", 9 | "not_written": "red", 10 | "control": "green4", 11 | "datatype": "brown", 12 | "function": "violet", 13 | "comment": "blue", 14 | } 15 | -------------------------------------------------------------------------------- /src/codegen/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains the exceptions for the code generation. 3 | """ 4 | 5 | 6 | class GenerationError(Exception): 7 | def __init__(self, caption, message: str | list[str]): 8 | self.caption = caption 9 | self.message = "\n".join(message) if isinstance(message, list) else message 10 | super().__init__(self.caption, self.message) 11 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | rev: v0.12.4 4 | hooks: 5 | - id: ruff 6 | args: [--fix, --unsafe-fixes] 7 | - id: ruff-format 8 | 9 | - repo: https://github.com/pre-commit/pre-commit-hooks 10 | rev: v4.5.0 11 | hooks: 12 | - id: trailing-whitespace 13 | - id: end-of-file-fixer 14 | - id: check-yaml 15 | - id: check-added-large-files -------------------------------------------------------------------------------- /tests/test_output/multiply_control_e.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: multiply_control_e.vhd 2 | -- Created by HDL-FSM-Editor 3 | library ieee; 4 | use ieee.std_logic_1164.all; 5 | 6 | entity multiply_control is 7 | generic ( 8 | g_counter_max : natural := 8 9 | ); 10 | port ( 11 | res_i : in std_logic; 12 | clk_i : in std_logic; 13 | start_i : in std_logic; 14 | last_step_o : out std_logic; 15 | ready_o : out std_logic; 16 | reg_enable_o : out std_logic 17 | ); 18 | end entity; 19 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pytest configuration for HDL-FSM-Editor tests. 3 | """ 4 | 5 | from pathlib import Path 6 | 7 | import pytest 8 | 9 | 10 | @pytest.fixture(scope="session") 11 | def project_root(): 12 | """Get the project root directory.""" 13 | return Path(__file__).parent.parent 14 | 15 | 16 | @pytest.fixture(scope="session") 17 | def test_output_dir(project_root): 18 | """Get the test output directory.""" 19 | output_dir = project_root / "tests" / "test_output" 20 | output_dir.mkdir(parents=True, exist_ok=True) 21 | return output_dir 22 | -------------------------------------------------------------------------------- /tests/test_output/square_root_control_e.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: square_root_control_e.vhd 2 | -- Created by HDL-FSM-Editor 3 | library ieee; 4 | use ieee.std_logic_1164.all; 5 | 6 | entity square_root_control is 7 | generic ( 8 | constant g_counter_max : natural := 16 9 | ); 10 | port ( 11 | clk_i : in std_logic; 12 | res_i : in std_logic; 13 | start_i : in std_logic; 14 | enable_reg_o : out std_logic; 15 | counter_o : out natural range 0 to g_counter_max; 16 | ready_steps_o : out std_logic; 17 | first_step_o : out std_logic 18 | ); 19 | end entity; 20 | -------------------------------------------------------------------------------- /src/widgets/OptionMenu.py: -------------------------------------------------------------------------------- 1 | """ 2 | Creates a menu. 3 | """ 4 | 5 | import tkinter as tk 6 | 7 | 8 | class OptionMenu(tk.Listbox): 9 | def __init__(self, master, items, *args, **kwargs) -> None: 10 | tk.Listbox.__init__(self, master, exportselection=False, background="grey", *args, **kwargs) 11 | 12 | for item in items: 13 | self.insert(tk.END, item) 14 | 15 | self.bind("", self.snap_highlight_to_mouse) 16 | self.bind("", self.snap_highlight_to_mouse) 17 | 18 | def snap_highlight_to_mouse(self, event) -> None: 19 | self.selection_clear(0, tk.END) 20 | self.selection_set(self.nearest(event.y)) 21 | -------------------------------------------------------------------------------- /tests/test_output/cordic_square_root_control_e.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: cordic_square_root_control_e.vhd 2 | -- Created by HDL-FSM-Editor 3 | library ieee; 4 | use ieee.std_logic_1164.all; 5 | 6 | entity cordic_square_root_control is 7 | generic ( 8 | constant g_counter_max : natural := 16 9 | ); 10 | port ( 11 | clk_i : in std_logic; 12 | res_i : in std_logic; 13 | start_cordic_i : in std_logic; 14 | enable_reg_o : out std_logic; 15 | counter_o : out natural range 0 to g_counter_max; 16 | ready_steps_o : out std_logic; 17 | first_step_o : out std_logic 18 | ); 19 | end entity; 20 | -------------------------------------------------------------------------------- /src/project.py: -------------------------------------------------------------------------------- 1 | """ 2 | Application state for HDL-FSM-Editor. 3 | This module contains the single state class that holds all application data. 4 | """ 5 | 6 | from dataclasses import dataclass 7 | 8 | 9 | @dataclass 10 | class Project: 11 | """Simple application state - everything in one place.""" 12 | 13 | # Project settings 14 | module_name: str = "" 15 | language: str = "VHDL" 16 | generate_path: str = "" 17 | working_directory: str = "" 18 | file_count: int = 1 19 | reset_signal_name: str = "reset" 20 | clock_signal_name: str = "clk" 21 | compile_command: str = "" 22 | edit_command: str = "" 23 | 24 | # File management moved to StateManager 25 | -------------------------------------------------------------------------------- /tests/test_output/count10_e.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: count10_e.vhd 2 | -- Created by HDL-FSM-Editor 3 | library ieee; 4 | use ieee.std_logic_1164.all; 5 | 6 | entity count10 is 7 | generic ( 8 | g_reset_value : std_logic := '0'; 9 | g_counter_width : integer := 4 10 | ); 11 | port ( 12 | res_i : in std_logic; 13 | clk_i : in std_logic; 14 | 15 | start_i : in std_logic; 16 | enable_i : in std_logic; 17 | disable_forever_i : in std_logic; 18 | counter_o : out std_logic_vector(g_counter_width-1 downto 0); 19 | running_o : out std_logic; 20 | ready_o : out std_logic; 21 | half_o : out std_logic 22 | ); 23 | end entity; 24 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[python]": { 3 | "python.linting.enabled": true, 4 | "python.linting.ruffEnabled": true, 5 | "python.formatting.provider": "ruff", 6 | "python.formatting.ruffArgs": [ 7 | "--fix" 8 | ], 9 | "editor.formatOnSave": true, 10 | "editor.codeActionsOnSave": { 11 | "source.organizeImports": "explicit", 12 | "source.fixAll": "explicit" 13 | } 14 | }, 15 | "python.testing.pytestArgs": [ 16 | "tests", 17 | "-n", 18 | "auto", 19 | "--tb=short" 20 | ], 21 | "python.testing.unittestEnabled": false, 22 | "python.testing.pytestEnabled": true, 23 | "python.testing.autoTestDiscoverOnSaveEnabled": true, 24 | "pylint.args": [ 25 | "--max-line-length=120", 26 | "--disable=C0116" 27 | ] 28 | } -------------------------------------------------------------------------------- /tests/test_output/fifo_control_e.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: fifo_control_e.vhd 2 | -- Created by HDL-FSM-Editor 3 | library ieee; 4 | use ieee.std_logic_1164.all; 5 | 6 | entity fifo_control is 7 | generic ( 8 | g_fifo_depth : natural := 16; 9 | g_ram_address_width : natural := 4 10 | ); 11 | port ( 12 | res_i : in std_logic; 13 | clk_i : in std_logic; 14 | write_fifo_i : in std_logic; 15 | read_fifo_i : in std_logic; 16 | fifo_underflow_o : out std_logic; 17 | fifo_overflow_o : out std_logic; 18 | ram_address_o : out std_logic_vector(g_ram_address_width-1 downto 0); 19 | fifo_empty_o : out std_logic; 20 | fifo_full_o : out std_logic; 21 | write_ram_o : out std_logic; 22 | last_o : out std_logic 23 | ); 24 | end entity; 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HDL-FSM-Editor 2 | A tool for modeling FSMs by VHDL or Verilog. 3 | 4 | Please visit [HDL-FSM-Editor](http://www.hdl-fsm-editor.de) for more information. 5 | 6 | ## Quick Start 7 | 8 | Clone the repository and run: 9 | ```bash 10 | python src/main.py 11 | ``` 12 | 13 | ## Development 14 | 15 | ### Setup 16 | Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and run: 17 | ```bash 18 | uv sync --dev 19 | ``` 20 | 21 | To find out how to build a full executable of the application, run: 22 | ```bash 23 | uv run release_script.py --help 24 | ``` 25 | 26 | ### VSCode Setup 27 | For the best development experience, install these VSCode extensions: 28 | - **Python** - Official Python extension 29 | - **Ruff** - Fast Python linter and formatter 30 | 31 | **Ensure to select the virtual environment** (`.venv`) created by uv for the full development experience. 32 | Press `Ctrl+Shift+P` and select `Python: Select Interpreter` to select the virtual environment. 33 | 34 | -------------------------------------------------------------------------------- /tests/test_output/uart_receive_e.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: uart_receive_e.vhd 2 | -- Created by HDL-FSM-Editor 3 | library ieee; 4 | use ieee.std_logic_1164.all; 5 | use ieee.numeric_std.all; 6 | 7 | entity uart_receive is 8 | generic ( 9 | constant g_divisor_width : natural := 8; 10 | constant g_data_width : natural := 8; 11 | constant g_has_parity : boolean := true; 12 | constant g_odd_parity : boolean := false 13 | ); 14 | port ( 15 | res_i : in std_logic; 16 | clk_i : in std_logic; 17 | rx_sync_i : in std_logic; 18 | receive_clock_enable_i : in std_logic; 19 | divisor_i : in unsigned(g_divisor_width-1 downto 0); 20 | enable_receive_clock_divider_o : out std_logic; 21 | data_o : out std_logic_vector(g_data_width-1 downto 0); 22 | parity_err_o : out std_logic; 23 | ready_receive_o : out std_logic 24 | ); 25 | end entity; 26 | -------------------------------------------------------------------------------- /tests/test_output/uart_send_e.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: uart_send_e.vhd 2 | -- Created by HDL-FSM-Editor 3 | library ieee; 4 | use ieee.std_logic_1164.all; 5 | 6 | entity uart_send is 7 | generic ( 8 | constant g_data_width : natural := 8; 9 | 10 | constant g_has_parity : boolean := true; 11 | constant g_odd_parity : boolean := false; -- If TRUE, the number of ones together in databits and paritybit must be odd, else even. 12 | constant g_number_of_stopbits : natural := 1 -- Allowed values: >= 1 13 | ); 14 | port ( 15 | res_i : in std_logic; 16 | clk_i : in std_logic; 17 | send_clock_enable_i : in std_logic; 18 | send_i : in std_logic; 19 | data_i : in std_logic_vector(g_data_width-1 downto 0); 20 | tx_o : out std_logic; 21 | ready_send_o : out std_logic; 22 | enable_send_clock_divider_o : out std_logic 23 | 24 | ); 25 | end entity; 26 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "hdl-fsm-editor" 3 | version = "0.1.0" 4 | description = "HDL-FSM-Editor for VHDL and Verilog" 5 | readme = "README.md" 6 | requires-python = ">=3.9" 7 | dependencies = [ 8 | "pre-commit>=4.2.0", 9 | ] 10 | 11 | [dependency-groups] 12 | dev = [ 13 | "pre-commit>=4.2.0", 14 | "pytest>=8.4.1", 15 | "ruff>=0.12.4", 16 | "mypy>=1.17.0", 17 | "pyinstaller>=6.14.2", 18 | "pytest-xdist>=3.8.0", 19 | ] 20 | 21 | [tool.ruff] 22 | 23 | # Allow lines to be as long as 120. 24 | line-length = 120 25 | 26 | [tool.ruff.lint] 27 | 28 | select = [ 29 | # pycodestyle 30 | "E", 31 | # Pyflakes 32 | "F", 33 | # pyupgrade 34 | "UP", 35 | # flake8-bugbear 36 | "B", 37 | # flake8-simplify 38 | "SIM", 39 | # isort 40 | "I", 41 | ] 42 | 43 | 44 | [tool.pytest.ini_options] 45 | testpaths = ["tests"] 46 | addopts = [ 47 | "-v", 48 | "--tb=short", 49 | "--strict-markers", 50 | "--disable-warnings", 51 | ] 52 | markers = [ 53 | "golden_file: marks tests as golden file tests", 54 | "batch_mode: marks tests as batch mode tests", 55 | ] 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 matthiasschweikart 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # HDL-FSM-Editor Test Suite 2 | 3 | This directory contains automated tests for HDL-FSM-Editor. 4 | 5 | ## Structure 6 | 7 | - `test_golden_file_generation.py`: Golden file tests (generates HDL from .hfe and checks output) 8 | - `conftest.py`: Pytest config and fixtures 9 | - `test_output/`: Output directory for generated files 10 | 11 | ## Running Tests 12 | 13 | - Run all tests: 14 | `pytest` 15 | 16 | - Run only golden file tests: 17 | `pytest -m golden_file` 18 | 19 | - Run only batch mode tests: 20 | `pytest -m batch_mode` 21 | 22 | - Verbose output: 23 | `pytest -v` 24 | 25 | - Run a specific test: 26 | `pytest tests/test_golden_file_generation.py` 27 | 28 | ## Test Types 29 | 30 | - **Golden File Tests:** 31 | Generate HDL from `.hfe` files in `examples/` and compare with expected output (ignoring timestamps). 32 | 33 | - **Batch Mode Tests:** 34 | Check batch mode operation, reproducible output (no timestamps), and correct headers. 35 | 36 | ## Adding Tests 37 | 38 | 1. Add new test files with `test_` prefix. 39 | 2. Use pytest markers (`@pytest.mark.golden_file`, `@pytest.mark.batch_mode`). 40 | 3. Place new `.hfe` files in `examples/`. 41 | 4. Add matching golden HDL files in `examples/` (no timestamps). 42 | 43 | ## Output 44 | 45 | Generated files are placed in `tests/test_output/` to keep the repo clean. -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Run HDL-FSM-Editor (Verilog)", 9 | "type": "debugpy", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/src/main.py", 12 | "console": "integratedTerminal", 13 | "args": [ 14 | "--no-version-check", 15 | "--no-message", 16 | "examples/division_unsigned_control.hfe" 17 | ] 18 | }, 19 | { 20 | "name": "Run HDL-FSM-Editor (VHDL)", 21 | "type": "debugpy", 22 | "request": "launch", 23 | "program": "${workspaceFolder}/src/main.py", 24 | "console": "integratedTerminal", 25 | "args": [ 26 | "--no-version-check", 27 | "--no-message", 28 | "examples/multiply_wt_booth_control.hfe" 29 | ] 30 | }, 31 | { 32 | "name": "Run HDL-FSM-Editor without args", 33 | "type": "debugpy", 34 | "request": "launch", 35 | "program": "${workspaceFolder}/src/main.py", 36 | "console": "integratedTerminal", 37 | "args": [] 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /src/project_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | State manager for HDL-FSM-Editor. 3 | This module provides a simple way to access application state throughout the application. 4 | """ 5 | 6 | from project import Project 7 | 8 | 9 | class ProjectManager: 10 | """Simple project manager - just holds the state and provides access.""" 11 | 12 | def __init__(self) -> None: 13 | self._project = Project() 14 | # File management 15 | self._current_file: str = "" 16 | self._previous_file: str = "" 17 | 18 | @property 19 | def project(self) -> Project: 20 | """Direct access to the project state.""" 21 | return self._project 22 | 23 | @property 24 | def current_file(self) -> str: 25 | """Get the current file path.""" 26 | return self._current_file 27 | 28 | @current_file.setter 29 | def current_file(self, value: str) -> str: 30 | """Set the current file path.""" 31 | self._current_file = value 32 | 33 | @property 34 | def previous_file(self) -> str: 35 | """Get the previous file path.""" 36 | return self._previous_file 37 | 38 | @previous_file.setter 39 | def previous_file(self, value: str) -> str: 40 | """Set the previous file path.""" 41 | self._previous_file = value 42 | 43 | def get(self, attr_name: str, default=None): 44 | """Get a state attribute.""" 45 | return getattr(self._project, attr_name, default) 46 | 47 | def set(self, attr_name: str, value) -> None: 48 | """Set a state attribute.""" 49 | setattr(self._project, attr_name, value) 50 | 51 | def update(self, **kwargs) -> None: 52 | """Update multiple state attributes.""" 53 | for key, value in kwargs.items(): 54 | self.set(key, value) 55 | 56 | def reset(self) -> None: 57 | """Reset state to initial values.""" 58 | self._project = Project() 59 | self._current_file = "" 60 | self._previous_file = "" 61 | 62 | 63 | project_manager = ProjectManager() 64 | -------------------------------------------------------------------------------- /tests/test_output/division_unsigned_control.v: -------------------------------------------------------------------------------- 1 | // Filename: division_unsigned_control.v 2 | // Created by HDL-FSM-Editor 3 | module division_unsigned_control 4 | #(parameter 5 | g_counter_max = 8 6 | ) 7 | ( 8 | input res_i, 9 | input clk_i, 10 | input start_i, 11 | output reg ready_o, 12 | output reg reg_enable_o 13 | ); 14 | reg [0:0] state; 15 | localparam 16 | idle = 0, 17 | run = 1; 18 | integer counter; 19 | always @(posedge clk_i or posedge res_i) begin: p_states 20 | if (res_i==1'b1) begin 21 | state <= idle; 22 | counter <= 0 ; 23 | ready_o <= 1'b0; 24 | end 25 | else begin 26 | // State Machine: 27 | case (state) 28 | idle: begin 29 | if (start_i==1'b1) begin 30 | ready_o <= 1'b0; 31 | if (counter==g_counter_max) begin 32 | ready_o <= 1'b1; 33 | counter <= 0 ; 34 | end else begin 35 | counter <= counter + 1; 36 | state <= run; 37 | end 38 | end else begin 39 | ready_o <= 1'b0; 40 | end 41 | end 42 | run: begin 43 | if (counter==g_counter_max) begin 44 | ready_o <= 1'b1; 45 | counter <= 0 ; 46 | state <= idle; 47 | end else begin 48 | counter <= counter + 1; 49 | end 50 | end 51 | default: 52 | ; 53 | endcase 54 | end 55 | end 56 | always @(state) begin: p_state_actions 57 | // State Actions: 58 | case (state) 59 | idle: begin 60 | reg_enable_o <= 1'b0; 61 | end 62 | run: begin 63 | reg_enable_o <= 1'b1; 64 | end 65 | default: 66 | ; 67 | endcase 68 | end 69 | endmodule 70 | -------------------------------------------------------------------------------- /src/grid_drawing.py: -------------------------------------------------------------------------------- 1 | """ 2 | This class draws a grid into the canvas. 3 | """ 4 | 5 | import canvas_editing 6 | import main_window 7 | 8 | 9 | class GridDraw: 10 | """ 11 | This class draws a grid into the canvas. 12 | """ 13 | 14 | def __init__(self, canvas) -> None: 15 | self.canvas = canvas 16 | 17 | def remove_grid(self) -> None: 18 | self.canvas.delete("grid_line") 19 | 20 | def draw_grid(self) -> None: 21 | if main_window.show_grid is True: 22 | visible_window = [ 23 | self.canvas.canvasx(0), 24 | self.canvas.canvasy(0), 25 | self.canvas.canvasx(self.canvas.winfo_width()), 26 | self.canvas.canvasy(self.canvas.winfo_height()), 27 | ] 28 | grid_size = canvas_editing.state_radius 29 | if grid_size > 8: 30 | self.__draw_horizontal_grid(grid_size, visible_window) 31 | self.__draw_vertical_grid(grid_size, visible_window) 32 | self.canvas.tag_lower("grid_line") 33 | 34 | def __draw_horizontal_grid(self, grid_size, visible_window) -> None: 35 | # An extra margin of 3*grid_size is used because otherwise there are sometimes too few grid-lines: 36 | x_min = visible_window[0] - visible_window[0] % grid_size - 3 * grid_size 37 | x_max = visible_window[2] + visible_window[2] % grid_size + 3 * grid_size 38 | y = visible_window[1] - visible_window[1] % grid_size - 3 * grid_size 39 | y_max = visible_window[3] + visible_window[3] % grid_size + 3 * grid_size 40 | while y < y_max: 41 | self.canvas.create_line(x_min, y, x_max, y, dash=(1, 1), fill="gray85", tags="grid_line") 42 | y += grid_size 43 | 44 | def __draw_vertical_grid(self, grid_size, visible_window) -> None: 45 | x = visible_window[0] - visible_window[0] % grid_size 46 | x_max = visible_window[2] + visible_window[2] % grid_size 47 | y_min = visible_window[1] - visible_window[1] % grid_size 48 | y_max = visible_window[3] + visible_window[3] % grid_size 49 | while x < x_max: 50 | self.canvas.create_line(x, y_min, x, y_max, dash=(1, 1), fill="gray85", tags="grid_line") 51 | x += grid_size 52 | -------------------------------------------------------------------------------- /tests/test_output/square_root_control_fsm.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: square_root_control_fsm.vhd 2 | -- Created by HDL-FSM-Editor 3 | 4 | architecture fsm of square_root_control is 5 | type t_state is (idle, count); 6 | signal state : t_state; 7 | signal counter : natural range 0 to g_counter_max; 8 | begin 9 | p_states: process (res_i, clk_i) 10 | begin 11 | if res_i='1' then 12 | state <= idle; 13 | ready_steps_o <= '0'; 14 | counter <= 0 ; 15 | first_step_o <= '1'; 16 | elsif rising_edge(clk_i) then 17 | -- State Machine: 18 | case state is 19 | when idle => 20 | ready_steps_o <= '0'; 21 | if start_i='1' then 22 | first_step_o <= '0'; 23 | if counter=g_counter_max then 24 | ready_steps_o <= '1'; 25 | counter <= 0 ; 26 | first_step_o <= '1'; 27 | else 28 | counter <= counter + 1; 29 | state <= count; 30 | end if; 31 | else 32 | end if; 33 | when count => 34 | if counter=g_counter_max then 35 | ready_steps_o <= '1'; 36 | counter <= 0 ; 37 | first_step_o <= '1'; 38 | state <= idle; 39 | else 40 | counter <= counter + 1; 41 | end if; 42 | end case; 43 | end if; 44 | end process; 45 | p_state_actions: process (start_i, state) 46 | begin 47 | -- State Actions: 48 | case state is 49 | when idle=> 50 | if start_i='1' then 51 | enable_reg_o <= '1'; 52 | else 53 | enable_reg_o <= '0'; 54 | end if; 55 | when count=> 56 | enable_reg_o <= '1'; 57 | end case; 58 | end process; 59 | -- Global Actions combinatorial: 60 | counter_o <= counter; 61 | end architecture; 62 | -------------------------------------------------------------------------------- /tests/test_output/cordic_square_root_control_fsm.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: cordic_square_root_control_fsm.vhd 2 | -- Created by HDL-FSM-Editor 3 | 4 | architecture fsm of cordic_square_root_control is 5 | type t_state is (count, idle); 6 | signal state : t_state; 7 | signal counter : natural range 0 to g_counter_max; 8 | begin 9 | p_states: process (res_i, clk_i) 10 | begin 11 | if res_i='1' then 12 | state <= idle; 13 | ready_steps_o <= '0'; 14 | counter <= 0; 15 | first_step_o <= '1'; 16 | elsif rising_edge(clk_i) then 17 | -- State Machine: 18 | case state is 19 | when count => 20 | if counter=g_counter_max then 21 | ready_steps_o <= '1'; 22 | counter <= 0; 23 | first_step_o <= '1'; 24 | state <= idle; 25 | else 26 | counter <= counter + 1; 27 | end if; 28 | when idle => 29 | ready_steps_o <= '0'; 30 | if start_cordic_i='1' then 31 | first_step_o <= '0'; 32 | if counter=g_counter_max then 33 | ready_steps_o <= '1'; 34 | counter <= 0; 35 | first_step_o <= '1'; 36 | else 37 | counter <= counter + 1; 38 | state <= count; 39 | end if; 40 | else 41 | end if; 42 | end case; 43 | end if; 44 | end process; 45 | p_state_actions: process (start_cordic_i, state) 46 | begin 47 | -- State Actions: 48 | case state is 49 | when count=> 50 | enable_reg_o <= '1'; 51 | when idle=> 52 | if start_cordic_i='1' then 53 | enable_reg_o <= '1'; 54 | else 55 | enable_reg_o <= '0'; 56 | end if; 57 | end case; 58 | end process; 59 | -- Global Actions combinatorial: 60 | counter_o <= counter; 61 | end architecture; 62 | -------------------------------------------------------------------------------- /src/move_handling_canvas_item.py: -------------------------------------------------------------------------------- 1 | """ 2 | A MoveHandlingCanvasItem object is created, when the user moves a state (touching state-circle or state-name). 3 | """ 4 | 5 | import canvas_modify_bindings 6 | import main_window 7 | import move_handling 8 | import move_handling_finish 9 | import move_handling_initialization 10 | import undo_handling 11 | 12 | 13 | class MoveHandlingCanvasItem: 14 | def __init__(self, event, canvas_id): 15 | if canvas_modify_bindings.transition_insertion_runs: 16 | return # Button-1 shall now not move any canvas item 17 | self.canvas_id = canvas_id 18 | self.move_list = move_handling_initialization.create_move_list([self.canvas_id], event.x, event.y) 19 | 20 | # This first move does not move the object. 21 | # It is needed to set self.difference_x, self.difference_y of the moved window to 0. 22 | # Both values are used, when the window is picked up at its border. 23 | # The values are set to 0 by using window_coords[0] and window_coords[1] as event coords: 24 | move_handling.move_to_coordinates( 25 | event.x, 26 | event.y, 27 | self.move_list, 28 | first=True, 29 | move_to_grid=False, 30 | ) 31 | 32 | # Create a binding for the now following movements of the mouse and for finishing the moving: 33 | self.funcid_motion = main_window.canvas.tag_bind(self.canvas_id, "", self._motion) 34 | self.funcid_release = main_window.canvas.tag_bind(self.canvas_id, "", self._release) 35 | 36 | def _motion(self, motion_event): 37 | move_handling.move_to_coordinates( 38 | motion_event.x, 39 | motion_event.y, 40 | self.move_list, 41 | first=False, 42 | move_to_grid=False, 43 | ) 44 | 45 | def _release(self, release_event): 46 | main_window.canvas.tag_unbind(self.canvas_id, "", self.funcid_motion) 47 | main_window.canvas.tag_unbind(self.canvas_id, "", self.funcid_release) 48 | move_handling.move_to_coordinates( 49 | release_event.x, 50 | release_event.y, 51 | self.move_list, 52 | first=False, 53 | move_to_grid=True, 54 | ) 55 | move_handling_finish.move_finish_for_transitions(self.move_list) 56 | undo_handling.design_has_changed() 57 | -------------------------------------------------------------------------------- /tests/test_output/multiply_wt_booth_control.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: multiply_wt_booth_control.vhd 2 | -- Created by HDL-FSM-Editor 3 | library ieee; 4 | use ieee.std_logic_1164.all; 5 | 6 | entity multiply_wt_booth_control is 7 | generic ( 8 | g_counter_max : natural := 8 9 | ); 10 | port ( 11 | res_i : in std_logic; 12 | clk_i : in std_logic; 13 | start_i : in std_logic; 14 | ready_o : out std_logic; 15 | reg_enable_o : out std_logic 16 | ); 17 | end entity; 18 | library ieee; 19 | use ieee.std_logic_1164.all; 20 | 21 | architecture fsm of multiply_wt_booth_control is 22 | type t_state is (idle, run); 23 | signal state : t_state; 24 | signal counter : natural range 0 to g_counter_max; 25 | begin 26 | p_states: process (res_i, clk_i) 27 | begin 28 | if res_i='1' then 29 | state <= idle; 30 | counter <= 0 ; 31 | ready_o <= '0'; 32 | elsif rising_edge(clk_i) then 33 | -- State Machine: 34 | case state is 35 | when idle => 36 | if start_i='1' then 37 | ready_o <= '0'; 38 | if counter=g_counter_max then 39 | ready_o <= '1'; 40 | counter <= 0 ; 41 | else 42 | counter <= counter + 1; 43 | state <= run; 44 | end if; 45 | else 46 | ready_o <= '0'; 47 | end if; 48 | when run => 49 | if counter=g_counter_max then 50 | ready_o <= '1'; 51 | counter <= 0 ; 52 | state <= idle; 53 | else 54 | counter <= counter + 1; 55 | end if; 56 | end case; 57 | end if; 58 | end process; 59 | p_state_actions: process (start_i, state) 60 | begin 61 | -- State Actions: 62 | case state is 63 | when idle=> 64 | if start_i='1' then 65 | reg_enable_o <= '1'; 66 | else 67 | reg_enable_o <= '0'; 68 | end if; 69 | when run=> 70 | reg_enable_o <= '1'; 71 | end case; 72 | end process; 73 | end architecture; 74 | -------------------------------------------------------------------------------- /tests/test_output/count10_fsm.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: count10_fsm.vhd 2 | -- Created by HDL-FSM-Editor 3 | library ieee; 4 | use ieee.std_logic_1164.all; 5 | use ieee.numeric_std.all; 6 | 7 | architecture fsm of count10 is 8 | type t_state is (disabled, running, idle); 9 | signal state : t_state; 10 | signal counter : unsigned(g_counter_width-1 downto 0); 11 | begin 12 | p_states: process (res_i, clk_i) 13 | begin 14 | if res_i='1' then 15 | state <= idle; 16 | counter <= (others => '0'); 17 | half_o <= '0'; 18 | elsif rising_edge(clk_i) then 19 | -- Global Actions before: 20 | half_o <= '0'; -- Default value 21 | -- State Machine: 22 | case state is 23 | when disabled => 24 | when running => 25 | if disable_forever_i='1' then 26 | state <= disabled; 27 | elsif counter=10 then 28 | counter <= (others => '0'); 29 | state <= idle; 30 | else 31 | if counter=5 then 32 | half_o <= '1'; 33 | end if; 34 | counter <= counter + 1; 35 | end if; 36 | when idle => 37 | -- test1 38 | -- test2 39 | if start_i='1' then 40 | counter <= counter + 1; 41 | state <= running; 42 | end if; 43 | end case; 44 | -- Global Actions after: 45 | if enable_i='0' then 46 | counter <= (others => '0'); 47 | else 48 | counter <= (others => '1'); 49 | end if; 50 | end if; 51 | end process; 52 | p_state_actions: process (start_i, counter, state) 53 | begin 54 | -- Default State Actions: 55 | running_o <= start_i; 56 | ready_o <= '0'; 57 | -- State Actions: 58 | case state is 59 | when disabled=> 60 | null; 61 | when running=> 62 | running_o <= '1'; 63 | if (counter=10) then 64 | ready_o <= '1'; 65 | end if; 66 | when idle=> 67 | null; 68 | end case; 69 | end process; 70 | -- Global Actions combinatorial: 71 | counter_o <= std_logic_vector(counter); 72 | end architecture; 73 | -------------------------------------------------------------------------------- /tests/test_output/multiply_control_fsm.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: multiply_control_fsm.vhd 2 | -- Created by HDL-FSM-Editor 3 | library ieee; 4 | use ieee.std_logic_1164.all; 5 | 6 | architecture fsm of multiply_control is 7 | type t_state is (idle, run); 8 | signal state : t_state; 9 | signal counter : natural range 0 to g_counter_max; 10 | signal last_step : std_logic; 11 | begin 12 | p_states: process (res_i, clk_i) 13 | begin 14 | if res_i='1' then 15 | state <= idle; 16 | counter <= 0 ; 17 | ready_o <= '0'; 18 | last_step <= '0'; 19 | elsif rising_edge(clk_i) then 20 | -- State Machine: 21 | case state is 22 | when idle => 23 | if start_i='1' then 24 | ready_o <= '0'; 25 | if counter=g_counter_max then 26 | ready_o <= '1'; 27 | counter <= 0 ; 28 | last_step <= '0'; 29 | else 30 | if counter=g_counter_max-1 then 31 | last_step <= '1'; 32 | end if; 33 | counter <= counter + 1; 34 | state <= run; 35 | end if; 36 | else 37 | ready_o <= '0'; 38 | end if; 39 | when run => 40 | if counter=g_counter_max then 41 | ready_o <= '1'; 42 | counter <= 0 ; 43 | last_step <= '0'; 44 | state <= idle; 45 | else 46 | if counter=g_counter_max-1 then 47 | last_step <= '1'; 48 | end if; 49 | counter <= counter + 1; 50 | end if; 51 | end case; 52 | end if; 53 | end process; 54 | p_state_actions: process (start_i, state) 55 | begin 56 | -- State Actions: 57 | case state is 58 | when idle=> 59 | if start_i='1' then 60 | reg_enable_o <= '1'; 61 | else 62 | reg_enable_o <= '0'; 63 | end if; 64 | when run=> 65 | reg_enable_o <= '1'; 66 | end case; 67 | end process; 68 | -- Global Actions combinatorial: 69 | last_step_o <= '1' when g_counter_max=0 else 70 | last_step; 71 | 72 | end architecture; 73 | -------------------------------------------------------------------------------- /src/vector_handling.py: -------------------------------------------------------------------------------- 1 | """ 2 | Methods for handling the form of transitions 3 | """ 4 | 5 | import math 6 | 7 | 8 | def shorten_vector(delta0, x0, y0, delta1, x1, y1, modify0, modify1) -> list: 9 | phi = math.pi / 2 if x1 - x0 == 0 else math.atan((y1 - y0) / (x1 - x0)) 10 | phi = abs(phi) 11 | delta0_x = delta0 * math.cos(phi) 12 | delta0_y = delta0 * math.sin(phi) 13 | delta1_x = delta1 * math.cos(phi) 14 | delta1_y = delta1 * math.sin(phi) 15 | if y1 >= y0 and x1 >= x0: 16 | return [x0 + delta0_x * modify0, y0 + delta0_y * modify0, x1 - delta1_x * modify1, y1 - delta1_y * modify1] 17 | elif y1 >= y0 and x1 < x0: 18 | return [x0 - delta0_x * modify0, y0 + delta0_y * modify0, x1 + delta1_x * modify1, y1 - delta1_y * modify1] 19 | elif y1 < y0 and x1 >= x0: 20 | return [x0 + delta0_x * modify0, y0 - delta0_y * modify0, x1 - delta1_x * modify1, y1 + delta1_y * modify1] 21 | else: 22 | return [x0 - delta0_x * modify0, y0 - delta0_y * modify0, x1 + delta1_x * modify1, y1 + delta1_y * modify1] 23 | 24 | 25 | def try_to_convert_into_straight_line(coords) -> list: 26 | number_of_points = len(coords) / 2 27 | if number_of_points == 2: 28 | return coords 29 | vector_list = _calculate_vectors_from_line_point_to_next_line_point(coords) 30 | cos_phi_list = _calculate_cos_phi_values_between_vectors(vector_list) 31 | eliminate_points = True 32 | for cos_phi in cos_phi_list: 33 | if cos_phi < 0.97: 34 | eliminate_points = False 35 | if eliminate_points: 36 | return [coords[0], coords[1], coords[-2], coords[-1]] 37 | else: 38 | return coords 39 | 40 | 41 | def _calculate_vectors_from_line_point_to_next_line_point(coords) -> list: 42 | vector_list = [] 43 | for i in range(len(coords) // 2 - 1): 44 | vector_from_point_to_point = _sub_vectors( 45 | coords[i * 2 + 2], coords[i * 2 + 3], coords[i * 2 + 0], coords[i * 2 + 1] 46 | ) 47 | if vector_from_point_to_point != [0, 0]: 48 | vector_list.append(vector_from_point_to_point) 49 | return vector_list 50 | 51 | 52 | def _calculate_cos_phi_values_between_vectors(vector_list) -> list: 53 | cos_phi_list = [] 54 | for i in range(len(vector_list) - 1): 55 | product_vector1_vector2 = _calculate_scalar_product( 56 | vector_list[i][0], vector_list[i][1], vector_list[i + 1][0], vector_list[i + 1][1] 57 | ) 58 | amount_vector1 = math.sqrt( 59 | _calculate_scalar_product(vector_list[i][0], vector_list[i][1], vector_list[i][0], vector_list[i][1]) 60 | ) 61 | amount_vector2 = math.sqrt( 62 | _calculate_scalar_product( 63 | vector_list[i + 1][0], vector_list[i + 1][1], vector_list[i + 1][0], vector_list[i + 1][1] 64 | ) 65 | ) 66 | cos_phi = product_vector1_vector2 / (amount_vector1 * amount_vector2) 67 | cos_phi_list.append(cos_phi) 68 | return cos_phi_list 69 | 70 | 71 | def _sub_vectors(x1, y1, x2, y2) -> list: 72 | return [x1 - x2, y1 - y2] 73 | 74 | 75 | def _calculate_scalar_product(x1, y1, x2, y2): 76 | return x1 * x2 + y1 * y2 77 | -------------------------------------------------------------------------------- /src/codegen/list_separation_check.py: -------------------------------------------------------------------------------- 1 | """ 2 | This class gets a string which contains a list, where the elements are separated with ';' (VHDL) or ',' (Verilog). 3 | If the user did end the last element of the list also with this separator, 4 | the class must remove the separator from the last element (as this is the only correct HDL syntax). 5 | Removing of this separator is not so easy, as the separator might also be used in comments which are placed in the list. 6 | For removing first a copy of the string is created where all block-comments are replaced by blanks. 7 | Then all comments at the end of each line contained in the copied string are also replaced by blanks. 8 | Last the characters in the copied string are analyzed starting at the end of the copied string: 9 | If the first character which is different from blank or return is not the separator, 10 | the search is ended and nothing done else, because the user did not insert an illegal separator. 11 | But if the character is the separator its index is used to replace it by blank in the original string, 12 | afterwards the search is ended. 13 | """ 14 | 15 | import re 16 | 17 | import codegen.hdl_generation_library as hdl_generation_library 18 | 19 | 20 | class ListSeparationCheck: 21 | def __init__(self, list_string, language) -> None: 22 | self.list_string = list_string 23 | if language == "VHDL": 24 | separator = ";" 25 | comment_identifier = "--" 26 | else: 27 | separator = "," 28 | comment_identifier = "//" 29 | list_string_without_block_comment = hdl_generation_library.remove_vhdl_block_comments(list_string) 30 | list_string_without_comments = self.__replace_all_comments_at_line_end( 31 | list_string_without_block_comment, comment_identifier 32 | ) 33 | self.__remove_illegal_separator(list_string_without_comments, separator) 34 | 35 | def get_fixed_list(self): 36 | return self.list_string 37 | 38 | def __replace_all_comments_at_line_end(self, list_string_without_block_comment, comment_identifier) -> str: 39 | list_array = list_string_without_block_comment.split("\n") 40 | list_string_without_comments = "" 41 | for line in list_array: 42 | list_string_without_comments += self.__replace_comment_at_line_end_by_blank(comment_identifier, line) + "\n" 43 | return list_string_without_comments[:-1] # remove last return 44 | 45 | def __replace_comment_at_line_end_by_blank(self, comment_identifier, line): 46 | match_object = re.search(comment_identifier + ".*", line) 47 | if match_object is not None: 48 | line = ( 49 | line[: match_object.span()[0]] 50 | + " " * (match_object.span()[1] - match_object.span()[0]) 51 | + line[match_object.span()[1] :] 52 | ) 53 | return line 54 | 55 | def __remove_illegal_separator(self, list_string_without_comments, separator) -> None: 56 | for index, char in enumerate(reversed(list_string_without_comments)): 57 | if char not in (" ", "\n"): 58 | if char == separator: 59 | self.__remove_character_by_blank(index) 60 | break 61 | 62 | def __remove_character_by_blank(self, index) -> None: 63 | if index == 0: 64 | self.list_string = self.list_string[: -index - 1] 65 | else: 66 | self.list_string = self.list_string[: -index - 1] + " " + self.list_string[-index:] 67 | -------------------------------------------------------------------------------- /src/global_actions_handling.py: -------------------------------------------------------------------------------- 1 | """ 2 | All methods to handle the global actions block in the diagram. 3 | """ 4 | 5 | import tkinter as tk 6 | 7 | import canvas_editing 8 | import global_actions 9 | import global_actions_combinatorial 10 | import main_window 11 | import state_actions_default 12 | import undo_handling 13 | 14 | global_actions_clocked_number = 0 15 | global_actions_combinatorial_number = 0 16 | state_actions_default_number = 0 17 | 18 | 19 | def insert_global_actions_clocked(event) -> None: 20 | global global_actions_clocked_number 21 | if global_actions_clocked_number == 0: # Only 1 global action is allowed. 22 | main_window.global_action_clocked_button.config(state=tk.DISABLED) 23 | global_actions_clocked_number += 1 24 | _insert_global_actions_clocked_in_canvas(event) 25 | undo_handling.design_has_changed() 26 | 27 | 28 | def _insert_global_actions_clocked_in_canvas(event) -> None: 29 | canvas_grid_coordinates_of_the_event = ( 30 | canvas_editing.translate_window_event_coordinates_in_rounded_canvas_coordinates(event) 31 | ) 32 | _create_global_actions_clocked(canvas_grid_coordinates_of_the_event) 33 | 34 | 35 | def _create_global_actions_clocked(canvas_grid_coordinates_of_the_event) -> None: 36 | ref = global_actions.GlobalActions( 37 | canvas_grid_coordinates_of_the_event[0], canvas_grid_coordinates_of_the_event[1], height=1, width=8, padding=1 38 | ) 39 | ref.tag() 40 | 41 | 42 | def insert_global_actions_combinatorial(event) -> None: 43 | global global_actions_combinatorial_number 44 | if global_actions_combinatorial_number == 0: # Only 1 global action is allowed. 45 | main_window.global_action_combinatorial_button.config(state=tk.DISABLED) 46 | global_actions_combinatorial_number += 1 47 | _insert_global_actions_combinatorial_in_canvas(event) 48 | undo_handling.design_has_changed() 49 | 50 | 51 | def _insert_global_actions_combinatorial_in_canvas(event) -> None: 52 | canvas_grid_coordinates_of_the_event = ( 53 | canvas_editing.translate_window_event_coordinates_in_rounded_canvas_coordinates(event) 54 | ) 55 | _create_global_actions_combinatorial(canvas_grid_coordinates_of_the_event) 56 | 57 | 58 | def _create_global_actions_combinatorial(canvas_grid_coordinates_of_the_event) -> None: 59 | ref = global_actions_combinatorial.GlobalActionsCombinatorial( 60 | canvas_grid_coordinates_of_the_event[0], canvas_grid_coordinates_of_the_event[1], height=1, width=8, padding=1 61 | ) 62 | ref.tag() 63 | 64 | 65 | def insert_state_actions_default(event) -> None: 66 | global state_actions_default_number 67 | if state_actions_default_number == 0: # Only 1 global action is allowed. 68 | main_window.state_action_default_button.config(state=tk.DISABLED) 69 | state_actions_default_number += 1 70 | _insert_state_actions_default_in_canvas(event) 71 | undo_handling.design_has_changed() 72 | 73 | 74 | def _insert_state_actions_default_in_canvas(event) -> None: 75 | canvas_grid_coordinates_of_the_event = ( 76 | canvas_editing.translate_window_event_coordinates_in_rounded_canvas_coordinates(event) 77 | ) 78 | _create_state_actions_default(canvas_grid_coordinates_of_the_event) 79 | 80 | 81 | def _create_state_actions_default(canvas_grid_coordinates_of_the_event) -> None: 82 | ref = state_actions_default.StateActionsDefault( 83 | canvas_grid_coordinates_of_the_event[0], canvas_grid_coordinates_of_the_event[1], height=1, width=8, padding=1 84 | ) 85 | ref.tag() 86 | -------------------------------------------------------------------------------- /src/canvas_modify_bindings.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains all methods to change the binding of the left mouse-button for 3 | inserting the different graphical objects. 4 | """ 5 | 6 | import canvas_editing 7 | import connector_handling 8 | import global_actions_handling 9 | import main_window 10 | import move_handling_initialization 11 | import reset_entry_handling 12 | import state_handling 13 | import transition_handling 14 | 15 | transition_insertion_runs = False 16 | 17 | 18 | def switch_to_state_insertion() -> None: 19 | global transition_insertion_runs 20 | transition_insertion_runs = False 21 | # From now on states can be inserted by left mouse button (this ends with the escape key): 22 | main_window.root.config(cursor="circle") 23 | main_window.canvas.bind("", state_handling.insert_state) 24 | 25 | 26 | def switch_to_transition_insertion() -> None: 27 | global transition_insertion_runs 28 | transition_insertion_runs = True 29 | # From now on transitions can be inserted by left mouse button (this ends with the escape key): 30 | main_window.root.config(cursor="cross") 31 | main_window.canvas.bind("", transition_handling.transition_start) 32 | 33 | 34 | def switch_to_connector_insertion() -> None: 35 | global transition_insertion_runs 36 | transition_insertion_runs = False 37 | # print("switch_to_connector_insertion") 38 | main_window.root.config(cursor="dot") 39 | main_window.canvas.bind("", connector_handling.insert_connector) 40 | 41 | 42 | def switch_to_reset_entry_insertion() -> None: 43 | global transition_insertion_runs 44 | transition_insertion_runs = False 45 | # print("switch_to_reset_entry_insertion") 46 | main_window.root.config(cursor="center_ptr") 47 | main_window.canvas.bind("", reset_entry_handling.insert_reset_entry) 48 | 49 | 50 | def switch_to_state_action_default_insertion() -> None: 51 | global transition_insertion_runs 52 | transition_insertion_runs = False 53 | # print("switch_to_state_action_default_insertion") 54 | main_window.root.config(cursor="bogosity") 55 | main_window.canvas.bind("", global_actions_handling.insert_state_actions_default) 56 | 57 | 58 | def switch_to_global_action_clocked_insertion() -> None: 59 | global transition_insertion_runs 60 | transition_insertion_runs = False 61 | # print("switch_to_global_action_clocked_insertion") 62 | main_window.root.config(cursor="bogosity") 63 | main_window.canvas.bind("", global_actions_handling.insert_global_actions_clocked) 64 | 65 | 66 | def switch_to_global_action_combinatorial_insertion() -> None: 67 | global transition_insertion_runs 68 | transition_insertion_runs = False 69 | # print("switch_to_global_action_combinatorial_insertion") 70 | main_window.root.config(cursor="bogosity") 71 | main_window.canvas.bind("", global_actions_handling.insert_global_actions_combinatorial) 72 | 73 | 74 | def switch_to_move_mode() -> None: 75 | global transition_insertion_runs 76 | transition_insertion_runs = False 77 | # print("switch_to_move_mode") 78 | main_window.root.config(cursor="arrow") 79 | main_window.canvas.focus_set() # Removes the focus from the last used button. 80 | main_window.canvas.bind("", move_handling_initialization.move_initialization) 81 | 82 | 83 | def switch_to_view_area() -> None: 84 | global transition_insertion_runs 85 | transition_insertion_runs = False 86 | # print("switch_to_view_area") 87 | main_window.root.config(cursor="plus") 88 | main_window.canvas.bind("", canvas_editing.start_view_rectangle) 89 | -------------------------------------------------------------------------------- /src/move_handling_canvas_window.py: -------------------------------------------------------------------------------- 1 | """ 2 | A MoveCanvasWindow object is created, when the user moves a Canvas window object. 3 | """ 4 | 5 | import main_window 6 | import move_handling 7 | import move_handling_finish 8 | import move_handling_initialization 9 | import undo_handling 10 | 11 | 12 | class MoveHandlingCanvasWindow: 13 | def __init__(self, event, widget, window_id): 14 | self.move_active = True 15 | self.widget = widget 16 | self.window_id = window_id 17 | window_coords = main_window.canvas.coords(self.window_id) 18 | self.move_list = move_handling_initialization.create_move_list( 19 | [self.window_id], window_coords[0], window_coords[1] 20 | ) 21 | self.touching_point_x = event.x 22 | self.touching_point_y = event.y 23 | 24 | # This first move does not move the object. 25 | # It is needed to set self.difference_x, self.difference_y of the moved window to 0. 26 | # Both values are used, when the window is picked up at its border. 27 | # The values are set to 0 by using window_coords[0] and window_coords[1] as event coords: 28 | move_handling.move_to_coordinates( 29 | window_coords[0], 30 | window_coords[1], 31 | self.move_list, 32 | first=True, 33 | move_to_grid=False, 34 | ) 35 | 36 | # Create a binding for the now following movements of the mouse and for finishing the moving: 37 | self.funcid_motion = self.widget.bind("", self._motion) 38 | self.funcid_release = self.widget.bind("", self._release) 39 | 40 | def _motion(self, motion_event): 41 | # At slow systems, tkinter needs some time to rearrange the canvas items before 42 | # it is able to give correct coords at events inside the Canvas window item. 43 | # So first do not listen to events anymore: 44 | self.widget.unbind("", self.funcid_motion) 45 | self.funcid_motion = None 46 | if not self.move_active: 47 | # The release event did already happen: 48 | return 49 | delta_x = motion_event.x - self.touching_point_x 50 | delta_y = motion_event.y - self.touching_point_y 51 | window_coords = main_window.canvas.coords(self.window_id) 52 | move_handling.move_to_coordinates( 53 | window_coords[0] + delta_x, 54 | window_coords[1] + delta_y, 55 | self.move_list, 56 | first=False, 57 | move_to_grid=False, 58 | ) 59 | # Later on, listen to events again: 60 | main_window.root.after(50, self._bind_motion_again) 61 | #main_window.root.after_idle(self._bind_motion_again) 62 | 63 | def _bind_motion_again(self): 64 | self.funcid_motion = self.widget.bind("", self._motion) 65 | 66 | def _release(self, _): 67 | self.move_active = False 68 | if self.funcid_motion is not None: 69 | self.widget.unbind("", self.funcid_motion) 70 | self.funcid_motion = None 71 | if self.funcid_release is not None: 72 | self.widget.unbind("", self.funcid_release) 73 | self.funcid_release = None 74 | window_coords = main_window.canvas.coords(self.window_id) 75 | move_handling.move_to_coordinates( 76 | window_coords[0], 77 | window_coords[1], 78 | self.move_list, 79 | first=False, 80 | move_to_grid=False, # Only used by the line to a window. 81 | ) 82 | move_handling_finish.move_finish_for_transitions(self.move_list) 83 | undo_handling.design_has_changed() 84 | -------------------------------------------------------------------------------- /tests/test_output/uart_send_fsm.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: uart_send_fsm.vhd 2 | -- Created by HDL-FSM-Editor 3 | 4 | architecture fsm of uart_send is 5 | type t_state is (idle, start_bit, data_bit, parity_bit, stop_bit, waiting); 6 | signal state : t_state; 7 | signal parity : std_logic; 8 | signal bit_counter : natural range 0 to g_data_width; 9 | begin 10 | p_states: process (res_i, clk_i) 11 | variable data : std_logic; 12 | begin 13 | if res_i='1' then 14 | state <= idle; 15 | tx_o <= '1'; 16 | ready_send_o <= '1'; 17 | parity <= '0'; 18 | bit_counter <= 0; 19 | enable_send_clock_divider_o <= '0'; 20 | elsif rising_edge(clk_i) then 21 | -- State Machine: 22 | case state is 23 | when idle => 24 | if send_i='1' then 25 | ready_send_o <= '0'; 26 | enable_send_clock_divider_o <= '1'; 27 | state <= waiting; 28 | end if; 29 | when start_bit => 30 | if send_clock_enable_i='1' then 31 | data := data_i(bit_counter); 32 | tx_o <= data; 33 | parity <= data xor parity; 34 | bit_counter <= bit_counter + 1; 35 | state <= data_bit; 36 | end if; 37 | when data_bit => 38 | if send_clock_enable_i='1' then 39 | if bit_counter=g_data_width then 40 | if g_has_parity=true then 41 | if g_odd_parity then 42 | tx_o <= parity xor '1'; 43 | else 44 | tx_o <= parity; 45 | end if; 46 | state <= parity_bit; 47 | else 48 | tx_o <= '1'; 49 | bit_counter <= 1; 50 | state <= stop_bit; 51 | end if; 52 | else 53 | data := data_i(bit_counter); 54 | tx_o <= data; 55 | parity <= data xor parity; 56 | bit_counter <= bit_counter + 1; 57 | end if; 58 | end if; 59 | when parity_bit => 60 | if send_clock_enable_i='1' then 61 | tx_o <= '1'; 62 | bit_counter <= 1; 63 | state <= stop_bit; 64 | end if; 65 | when stop_bit => 66 | if send_clock_enable_i='1' then 67 | if bit_counter=g_number_of_stopbits then 68 | ready_send_o <= '1'; 69 | parity <= '0'; 70 | bit_counter <= 0; 71 | enable_send_clock_divider_o <= '0'; 72 | state <= idle; 73 | else 74 | bit_counter <= bit_counter + 1; 75 | end if; 76 | end if; 77 | when waiting => 78 | if send_clock_enable_i='1' then 79 | tx_o <= '0'; 80 | state <= start_bit; 81 | end if; 82 | end case; 83 | end if; 84 | end process; 85 | end architecture; 86 | -------------------------------------------------------------------------------- /src/connector_handling.py: -------------------------------------------------------------------------------- 1 | """ 2 | Methods for the handling of the connectors 3 | """ 4 | 5 | import canvas_editing 6 | import constants 7 | import main_window 8 | import undo_handling 9 | 10 | connector_number = 0 11 | difference_x = 0 12 | difference_y = 0 13 | 14 | 15 | def insert_connector(event) -> None: 16 | global connector_number 17 | connector_number += 1 18 | # Translate the window coordinate into the canvas coordinate (the Canvas is bigger than the window): 19 | event_x, event_y = canvas_editing.translate_window_event_coordinates_in_rounded_canvas_coordinates(event) 20 | overlapping_items = main_window.canvas.find_overlapping( 21 | event_x - canvas_editing.state_radius / 2, 22 | event_y - canvas_editing.state_radius / 2, 23 | event_x + canvas_editing.state_radius / 2, 24 | event_y + canvas_editing.state_radius / 2, 25 | ) 26 | for overlapping_item in overlapping_items: 27 | if "grid_line" not in main_window.canvas.gettags(overlapping_item): 28 | return 29 | coords = ( 30 | event_x - canvas_editing.state_radius / 4, 31 | event_y - canvas_editing.state_radius / 4, 32 | event_x + canvas_editing.state_radius / 4, 33 | event_y + canvas_editing.state_radius / 4, 34 | ) 35 | tag = "connector" + str(connector_number) 36 | draw_connector(coords, tag) 37 | undo_handling.design_has_changed() 38 | 39 | 40 | def draw_connector(coords, tags): 41 | connector_id = main_window.canvas.create_rectangle(coords, fill=constants.CONNECTOR_COLOR, tags=tags) 42 | main_window.canvas.tag_bind( 43 | connector_id, "", lambda event, id=connector_id: main_window.canvas.itemconfig(id, width=2) 44 | ) 45 | main_window.canvas.tag_bind( 46 | connector_id, "", lambda event, id=connector_id: main_window.canvas.itemconfig(id, width=1) 47 | ) 48 | return connector_id 49 | 50 | 51 | def move_to(event_x, event_y, rectangle_id, first, last) -> None: 52 | global difference_x, difference_y 53 | if first is True: 54 | # Calculate the difference between the "anchor" point and the event: 55 | coords = main_window.canvas.coords(rectangle_id) 56 | middle_point = _calculate_middle_point(coords) 57 | difference_x, difference_y = -event_x + middle_point[0], -event_y + middle_point[1] 58 | # Keep the distance between event and anchor point constant: 59 | event_x, event_y = event_x + difference_x, event_y + difference_y 60 | if last is True: 61 | event_x = canvas_editing.state_radius * round(event_x / canvas_editing.state_radius) 62 | event_y = canvas_editing.state_radius * round(event_y / canvas_editing.state_radius) 63 | edge_length = _determine_edge_length_of_the_rectangle(rectangle_id) 64 | new_upper_left_corner = _calculate_new_upper_left_corner_of_the_rectangle(event_x, event_y, edge_length) 65 | new_lower_right_corner = _calculate_new_lower_right_corner_of_the_rectangle(event_x, event_y, edge_length) 66 | _move_rectangle_in_canvas(rectangle_id, new_upper_left_corner, new_lower_right_corner) 67 | 68 | 69 | def _calculate_middle_point(coords) -> list: 70 | middle_x = (coords[0] + coords[2]) / 2 71 | middle_y = (coords[1] + coords[3]) / 2 72 | return [middle_x, middle_y] 73 | 74 | 75 | def _determine_edge_length_of_the_rectangle(rectangle_id): 76 | rectangle_coords = main_window.canvas.coords(rectangle_id) 77 | edge_length = rectangle_coords[2] - rectangle_coords[0] 78 | return edge_length 79 | 80 | 81 | def _calculate_new_upper_left_corner_of_the_rectangle(event_x, event_y, edge_length) -> list: 82 | return [event_x - edge_length / 2, event_y - edge_length / 2] 83 | 84 | 85 | def _calculate_new_lower_right_corner_of_the_rectangle(event_x, event_y, edge_length) -> list: 86 | return [event_x + edge_length / 2, event_y + edge_length / 2] 87 | 88 | 89 | def _move_rectangle_in_canvas(rectangle_id, new_upper_left_corner, new_lower_right_corner) -> None: 90 | main_window.canvas.coords(rectangle_id, *new_upper_left_corner, *new_lower_right_corner) 91 | -------------------------------------------------------------------------------- /tests/test_output/fifo_control_fsm.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: fifo_control_fsm.vhd 2 | -- Created by HDL-FSM-Editor 3 | library ieee; 4 | use ieee.numeric_std.all; 5 | 6 | architecture fsm of fifo_control is 7 | type t_state is (filled, empty); 8 | signal state : t_state; 9 | signal write_address : natural range 0 to g_fifo_depth-1; 10 | signal write_address_plus1 : natural range 0 to g_fifo_depth-1; 11 | signal read_address : natural range 0 to g_fifo_depth-1; 12 | signal read_address_plus1 : natural range 0 to g_fifo_depth-1; 13 | signal read_address_plus2 : natural range 0 to g_fifo_depth-1; 14 | signal fifo_empty : boolean; 15 | signal fifo_full : boolean; 16 | begin 17 | p_states: process (res_i, clk_i) 18 | begin 19 | if res_i='1' then 20 | state <= empty; 21 | write_address <= 0; 22 | read_address <= 0; 23 | fifo_underflow_o <= '0'; 24 | fifo_overflow_o <= '0'; 25 | last_o <= '0'; 26 | elsif rising_edge(clk_i) then 27 | -- State Machine: 28 | case state is 29 | when filled => 30 | if write_fifo_i='1' then 31 | last_o <= '0'; 32 | if write_address=read_address then 33 | fifo_overflow_o <= '1'; 34 | else 35 | write_address <= write_address_plus1; 36 | end if; 37 | elsif read_fifo_i='1' then 38 | read_address <= read_address_plus1; 39 | if read_address_plus1=write_address then 40 | last_o <= '0'; 41 | state <= empty; 42 | else 43 | if read_address_plus2=write_address then 44 | last_o <= '1'; 45 | end if; 46 | end if; 47 | else 48 | fifo_overflow_o <= '0'; 49 | end if; 50 | when empty => 51 | if write_fifo_i='1' then 52 | write_address <= write_address_plus1; 53 | last_o <= '1'; 54 | fifo_underflow_o <= '0'; 55 | state <= filled; 56 | elsif read_fifo_i='1' then 57 | fifo_underflow_o <= '1'; 58 | else 59 | fifo_underflow_o <= '0'; 60 | end if; 61 | end case; 62 | end if; 63 | end process; 64 | p_state_actions: process (write_address, read_address, state) 65 | begin 66 | -- Default State Actions: 67 | fifo_empty <= false; 68 | fifo_full <= false; 69 | -- State Actions: 70 | case state is 71 | when filled=> 72 | if write_address=read_address then 73 | fifo_full <= true; 74 | end if; 75 | when empty=> 76 | fifo_empty <= true; 77 | end case; 78 | end process; 79 | -- Global Actions combinatorial: 80 | write_address_plus1 <= (write_address + 1) mod g_fifo_depth; 81 | read_address_plus1 <= (read_address + 1) mod g_fifo_depth; 82 | read_address_plus2 <= (read_address_plus1 + 1) mod g_fifo_depth; 83 | process(write_fifo_i, write_address, read_address_plus1) 84 | begin 85 | if write_fifo_i='1' then 86 | ram_address_o <= std_logic_vector(to_unsigned(write_address, g_ram_address_width)); 87 | else 88 | ram_address_o <= std_logic_vector(to_unsigned(read_address_plus1, g_ram_address_width)); 89 | end if; 90 | end process; 91 | write_ram_o <= write_fifo_i when not fifo_full else '0'; 92 | fifo_full_o <= '1' when fifo_full else '0'; 93 | fifo_empty_o <= '1' when fifo_empty else '0'; 94 | end architecture; 95 | -------------------------------------------------------------------------------- /src/codegen/hdl_generation_config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Configuration class for HDL generation settings 3 | """ 4 | 5 | from pathlib import Path 6 | from typing import Optional 7 | 8 | 9 | class GenerationConfig: 10 | """Configuration for HDL code generation""" 11 | 12 | def __init__(self) -> None: 13 | # Core generation settings 14 | self.language: str = "VHDL" # "VHDL", "Verilog", "SystemVerilog" 15 | self.module_name: str = "" 16 | self.generate_path: str = "" 17 | self.select_file_number: int = 1 # 1 or 2 files (VHDL only) 18 | self.include_timestamp: bool = False 19 | 20 | # Signal names 21 | self.clock_signal_name: str = "" 22 | self.reset_signal_name: str = "" 23 | 24 | # Code style 25 | self.indentation: str = " " # 4 spaces 26 | 27 | self.write_to_file: bool = False 28 | 29 | @classmethod 30 | def from_main_window(cls) -> "GenerationConfig": 31 | """Create a GenerationConfig instance from main_window settings""" 32 | import main_window 33 | 34 | config = cls() 35 | config.language = main_window.language.get() 36 | config.module_name = main_window.module_name.get() 37 | config.generate_path = main_window.generate_path_value.get() 38 | config.select_file_number = main_window.select_file_number_text.get() 39 | config.include_timestamp = main_window.include_timestamp_in_output.get() 40 | config.clock_signal_name = main_window.clock_signal_name.get() 41 | config.reset_signal_name = main_window.reset_signal_name.get() 42 | 43 | return config 44 | 45 | def get_comment_style(self) -> str: 46 | """Get the appropriate comment style for the current language""" 47 | if self.language == "VHDL": 48 | return "--" 49 | else: # Verilog, SystemVerilog 50 | return "//" 51 | 52 | def get_file_extension(self) -> str: 53 | """Get the appropriate file extension for the current language""" 54 | if self.language == "VHDL": 55 | return ".vhd" 56 | elif self.language == "Verilog": 57 | return ".v" 58 | elif self.language == "SystemVerilog": 59 | return ".sv" 60 | else: 61 | raise ValueError(f"Unsupported language: {self.language}") 62 | 63 | def get_output_files(self) -> list[str]: 64 | """ 65 | Get the list of output file paths based on language and file count settings. 66 | Returns a list of file paths that will be generated. 67 | """ 68 | if not self.module_name or not self.generate_path: 69 | return [] 70 | 71 | # For Verilog and SystemVerilog, always generate single files 72 | if self.language in ["Verilog", "SystemVerilog"]: 73 | extension = self.get_file_extension() 74 | return [f"{self.generate_path}/{self.module_name}{extension}"] 75 | 76 | # For VHDL, check file count setting 77 | if self.select_file_number == 1: 78 | # Single file 79 | return [f"{self.generate_path}/{self.module_name}.vhd"] 80 | else: 81 | # Two files: entity and architecture 82 | return [ 83 | f"{self.generate_path}/{self.module_name}_e.vhd", 84 | f"{self.generate_path}/{self.module_name}_fsm.vhd", 85 | ] 86 | 87 | def get_primary_file(self) -> Optional[str]: 88 | """Get the primary output file path (first file in the list)""" 89 | files = self.get_output_files() 90 | return files[0] if files else None 91 | 92 | def get_architecture_file(self) -> Optional[str]: 93 | """Get the architecture file path (for VHDL two-file mode)""" 94 | files = self.get_output_files() 95 | return files[1] if len(files) > 1 else None 96 | 97 | def validate(self) -> list[str]: 98 | """ 99 | Validate the configuration and return a list of error messages. 100 | Returns empty list if configuration is valid. 101 | """ 102 | errors = [] 103 | 104 | if not self.module_name or self.module_name.isspace(): 105 | errors.append("No module name is specified") 106 | 107 | if not self.generate_path or self.generate_path.isspace(): 108 | errors.append("No output path is specified") 109 | else: 110 | path = Path(self.generate_path) 111 | if not path.exists(): 112 | errors.append(f"Output path does not exist: {self.generate_path}") 113 | 114 | if not self.reset_signal_name or self.reset_signal_name.isspace(): 115 | errors.append("No reset signal name is specified") 116 | 117 | if not self.clock_signal_name or self.clock_signal_name.isspace(): 118 | errors.append("No clock signal name is specified") 119 | 120 | if self.language not in ["VHDL", "Verilog", "SystemVerilog"]: 121 | errors.append(f"Unsupported language: {self.language}") 122 | 123 | if self.select_file_number not in [1, 2]: 124 | errors.append(f"Invalid file number setting: {self.select_file_number}") 125 | 126 | return errors 127 | -------------------------------------------------------------------------------- /src/state_actions_default.py: -------------------------------------------------------------------------------- 1 | """ 2 | Handles the combinatorial default actions for all states. 3 | """ 4 | 5 | import tkinter as tk 6 | from tkinter import ttk 7 | 8 | import canvas_editing 9 | import canvas_modify_bindings 10 | import custom_text 11 | import main_window 12 | import move_handling_canvas_window 13 | import undo_handling 14 | 15 | 16 | class StateActionsDefault: 17 | """ 18 | Handles the combinatorial default actions for all states. 19 | """ 20 | 21 | dictionary = {} 22 | 23 | def __init__(self, menu_x, menu_y, height, width, padding) -> None: 24 | self.text_content = None 25 | self.frame_id = ttk.Frame( 26 | main_window.canvas, relief=tk.FLAT, borderwidth=0, padding=padding, style="StateActionsWindow.TFrame" 27 | ) # , borderwidth=10) 28 | self.frame_id.bind("", lambda event: self.activate_frame()) 29 | self.frame_id.bind("", lambda event: self.deactivate_frame()) 30 | # Create label object inside frame: 31 | self.label = ttk.Label( 32 | self.frame_id, 33 | text="Default state actions (combinatorial): ", 34 | font=("Arial", int(canvas_editing.label_fontsize)), 35 | style="StateActionsWindow.TLabel", 36 | ) 37 | self.label.bind("", lambda event: self.activate_window()) 38 | self.label.bind("", lambda event: self.deactivate_window()) 39 | self.text_id = custom_text.CustomText( 40 | self.frame_id, 41 | text_type="action", 42 | height=height, 43 | width=width, 44 | undo=True, 45 | maxundo=-1, 46 | font=("Courier", int(canvas_editing.fontsize)), 47 | ) 48 | self.text_id.bind("", lambda event: self.text_id.undo()) 49 | self.text_id.bind("", lambda event: self.text_id.redo()) 50 | self.text_id.bind("", lambda event: self._edit_in_external_editor()) 51 | self.text_id.bind("", lambda event: self.update_text()) 52 | self.text_id.bind("", lambda event: self.update_text()) 53 | self.text_id.bind("<>", lambda event: undo_handling.update_window_title()) 54 | self.text_id.bind("", lambda event: main_window.canvas.unbind_all("")) 55 | self.text_id.bind( 56 | "", lambda event: main_window.canvas.bind_all("", lambda event: canvas_editing.delete()) 57 | ) 58 | 59 | self.label.grid(row=0, column=0, sticky=(tk.N, tk.W, tk.E)) 60 | self.text_id.grid(row=1, column=0, sticky=(tk.E, tk.W)) 61 | 62 | self.difference_x = 0 63 | self.difference_y = 0 64 | self.move_rectangle = None 65 | 66 | # Create canvas window for frame and text: 67 | self.window_id = main_window.canvas.create_window(menu_x, menu_y, window=self.frame_id, anchor=tk.W) 68 | 69 | self.frame_id.bind( 70 | "", 71 | lambda event: move_handling_canvas_window.MoveHandlingCanvasWindow(event, self.frame_id, self.window_id), 72 | ) 73 | self.label.bind( 74 | "", 75 | lambda event: move_handling_canvas_window.MoveHandlingCanvasWindow(event, self.label, self.window_id), 76 | ) 77 | StateActionsDefault.dictionary[self.window_id] = self 78 | canvas_modify_bindings.switch_to_move_mode() 79 | 80 | def tag(self) -> None: 81 | main_window.canvas.itemconfigure(self.window_id, tag="state_actions_default") 82 | 83 | def _edit_in_external_editor(self): 84 | self.text_id.edit_in_external_editor() 85 | self.update_text() 86 | 87 | def update_text(self) -> None: 88 | # Update self.text_content, so that the -check in deactivate_frame() does not signal a design-change and 89 | # that save_in_file_new() already reads the new text, entered into the textbox before Control-s/g. 90 | # To ensure this, save_in_file_new() waits for idle. 91 | self.text_content = self.text_id.get("1.0", tk.END) 92 | 93 | def activate_frame(self) -> None: 94 | self.activate_window() 95 | self.text_content = self.text_id.get("1.0", tk.END) 96 | 97 | def activate_window(self) -> None: 98 | self.frame_id.configure(borderwidth=1, style="StateActionsWindowSelected.TFrame") 99 | self.label.configure(style="StateActionsWindowSelected.TLabel") 100 | 101 | def deactivate_frame(self) -> None: 102 | self.deactivate_window() 103 | self.frame_id.focus() # "unfocus" the Text, when the mouse leaves the text. 104 | if self.text_id.get("1.0", tk.END) != self.text_content: 105 | undo_handling.design_has_changed() 106 | 107 | def deactivate_window(self) -> None: 108 | self.frame_id.configure(borderwidth=0, style="StateActionsWindow.TFrame") 109 | self.label.configure(style="StateActionsWindow.TLabel") 110 | 111 | def move_to(self, event_x, event_y, first) -> None: 112 | if first: 113 | # Calculate the difference between the "anchor" point and the event: 114 | coords = main_window.canvas.coords(self.window_id) 115 | self.difference_x, self.difference_y = -event_x + coords[0], -event_y + coords[1] 116 | # Keep the distance between event and anchor point constant: 117 | event_x, event_y = event_x + self.difference_x, event_y + self.difference_y 118 | main_window.canvas.coords(self.window_id, event_x, event_y) 119 | -------------------------------------------------------------------------------- /src/global_actions_combinatorial.py: -------------------------------------------------------------------------------- 1 | """ 2 | Class for combinatorial actions independent from the state machine 3 | """ 4 | 5 | import tkinter as tk 6 | from tkinter import ttk 7 | 8 | import canvas_editing 9 | import canvas_modify_bindings 10 | import custom_text 11 | import main_window 12 | import move_handling_canvas_window 13 | import undo_handling 14 | 15 | 16 | class GlobalActionsCombinatorial: 17 | """ 18 | Class for combinatorial actions independent from the state machine 19 | """ 20 | 21 | dictionary = {} 22 | 23 | def __init__(self, menu_x, menu_y, height, width, padding) -> None: 24 | self.text_content = None 25 | self.difference_x = 0 26 | self.difference_y = 0 27 | 28 | self.frame_id = ttk.Frame( 29 | main_window.canvas, relief=tk.FLAT, borderwidth=0, padding=padding, style="GlobalActionsWindow.TFrame" 30 | ) 31 | self.frame_id.bind("", lambda event: self.activate_frame()) 32 | self.frame_id.bind("", lambda event: self.deactivate_frame()) 33 | # Create label object inside frame: 34 | self.label = ttk.Label( 35 | self.frame_id, 36 | text="Global actions combinatorial: ", 37 | font=("Arial", int(canvas_editing.label_fontsize)), 38 | style="GlobalActionsWindow.TLabel", 39 | ) 40 | self.label.bind("", lambda event: self.activate_window()) 41 | self.label.bind("", lambda event: self.deactivate_window()) 42 | self.text_id = custom_text.CustomText( 43 | self.frame_id, 44 | text_type="action", 45 | height=height, 46 | width=width, 47 | undo=True, 48 | maxundo=-1, 49 | font=("Courier", int(canvas_editing.fontsize)), 50 | ) 51 | self.text_id.bind("", lambda event: self.text_id.undo()) 52 | self.text_id.bind("", lambda event: self.text_id.redo()) 53 | self.text_id.bind("", lambda event: self._edit_in_external_editor()) 54 | self.text_id.bind("", lambda event: self.update_text()) 55 | self.text_id.bind("", lambda event: self.update_text()) 56 | self.text_id.bind("<>", lambda event: undo_handling.update_window_title()) 57 | self.text_id.bind("", lambda event: main_window.canvas.unbind_all("")) 58 | self.text_id.bind( 59 | "", lambda event: main_window.canvas.bind_all("", lambda event: canvas_editing.delete()) 60 | ) 61 | 62 | self.label.grid(row=0, column=0, sticky=(tk.N, tk.W, tk.E)) 63 | self.text_id.grid(row=1, column=0, sticky=(tk.E, tk.W)) 64 | 65 | # Create canvas window for frame and text: 66 | self.window_id = main_window.canvas.create_window(menu_x, menu_y, window=self.frame_id, anchor=tk.W) 67 | 68 | self.frame_id.bind( 69 | "", 70 | lambda event: move_handling_canvas_window.MoveHandlingCanvasWindow(event, self.frame_id, self.window_id), 71 | ) 72 | self.label.bind( 73 | "", 74 | lambda event: move_handling_canvas_window.MoveHandlingCanvasWindow(event, self.label, self.window_id), 75 | ) 76 | self.frame_id.lower() 77 | GlobalActionsCombinatorial.dictionary[self.window_id] = self 78 | canvas_modify_bindings.switch_to_move_mode() 79 | 80 | def _edit_in_external_editor(self): 81 | self.text_id.edit_in_external_editor() 82 | self.update_text() 83 | 84 | def update_text(self): 85 | # Update self.text_content, so that the -check in deactivate() does not signal a design-change and 86 | # that save_in_file_new() already reads the new text, entered into the textbox before Control-s/g. 87 | # To ensure this, save_in_file_new() waits for idle. 88 | self.text_content = self.text_id.get("1.0", tk.END) 89 | 90 | def tag(self) -> None: 91 | main_window.canvas.itemconfigure(self.window_id, tag="global_actions_combinatorial1") 92 | 93 | def activate_frame(self) -> None: 94 | self.activate_window() 95 | self.text_content = self.text_id.get("1.0", tk.END) 96 | 97 | def activate_window(self) -> None: 98 | self.frame_id.configure(borderwidth=1, style="GlobalActionsWindowSelected.TFrame") 99 | self.label.configure(style="GlobalActionsWindowSelected.TLabel") 100 | 101 | def deactivate_frame(self) -> None: 102 | self.deactivate_window() 103 | self.frame_id.focus() # "unfocus" the Text, when the mouse leaves the text. 104 | if self.text_id.get("1.0", tk.END) != self.text_content: 105 | undo_handling.design_has_changed() 106 | 107 | def deactivate_window(self) -> None: 108 | self.frame_id.configure(borderwidth=0, style="GlobalActionsWindow.TFrame") 109 | self.label.configure(style="GlobalActionsWindow.TLabel") 110 | 111 | def move_to(self, event_x, event_y, first) -> None: 112 | if first: 113 | # Calculate the difference between the "anchor" point and the event: 114 | coords = main_window.canvas.coords(self.window_id) 115 | self.difference_x, self.difference_y = -event_x + coords[0], -event_y + coords[1] 116 | # Keep the distance between event and anchor point constant: 117 | event_x, event_y = event_x + self.difference_x, event_y + self.difference_y 118 | main_window.canvas.coords(self.window_id, event_x, event_y) 119 | -------------------------------------------------------------------------------- /tests/test_output/regression2_fsm.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: regression2_fsm.vhd 2 | -- Created by HDL-FSM-Editor 3 | 4 | architecture fsm of regression2 is 5 | type t_state is (S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, 6 | S11, S12, S13, S14, S15, S16, S17, S18); 7 | signal state : t_state; 8 | begin 9 | p_states: process (res_i, clk_i) 10 | begin 11 | if res_i='1' then 12 | state <= S1; 13 | elsif rising_edge(clk_i) then 14 | -- State Machine: 15 | case state is 16 | when S1 => 17 | state <= S2; 18 | when S2 => 19 | action1 20 | state <= S3; 21 | when S3 => 22 | if cond2 then 23 | state <= S4; 24 | end if; 25 | when S4 => 26 | if cond3 then 27 | action3 28 | state <= S5; 29 | end if; 30 | when S5 => 31 | state <= S6; 32 | when S6 => 33 | if cond4 then 34 | state <= S7; 35 | else 36 | state <= S8; 37 | end if; 38 | when S7 => 39 | if cond5 then 40 | action5 41 | state <= S10; 42 | else 43 | action51 44 | state <= S9; 45 | end if; 46 | when S8 => 47 | if cond8 then 48 | if cond10 then 49 | action10 50 | if cond11 then 51 | state <= S15; 52 | else 53 | action12 54 | state <= S14; 55 | end if; 56 | else 57 | state <= S13; 58 | end if; 59 | end if; 60 | when S9 => 61 | if cond10 then 62 | action10 63 | if cond11 then 64 | state <= S15; 65 | else 66 | action12 67 | state <= S14; 68 | end if; 69 | else 70 | state <= S13; 71 | end if; 72 | when S10 => 73 | if cond6 then 74 | action6 75 | if cond7 then 76 | action7 77 | state <= S11; 78 | else 79 | state <= S12; 80 | end if; 81 | else 82 | state <= S9; 83 | end if; 84 | when S11 => 85 | if cond12 then 86 | else 87 | state <= S5; 88 | end if; 89 | when S12 => 90 | if cond20 then 91 | action20 92 | state <= S15; 93 | if cond21 then 94 | action21 95 | action30 96 | if cond22 then 97 | action22 98 | else 99 | action22_else 100 | end if; 101 | elsif cond24 then 102 | action23 103 | action24 104 | action30 105 | elsif cond25 then 106 | action23 107 | action25 108 | action30 109 | else 110 | action23 111 | end if; 112 | elsif cond10 then 113 | action9 114 | action10 115 | if cond11 then 116 | state <= S15; 117 | else 118 | action12 119 | state <= S14; 120 | end if; 121 | else 122 | action9 123 | state <= S13; 124 | end if; 125 | when S13 => 126 | when S14 => 127 | state <= S16; 128 | when S15 => 129 | when S16 => 130 | naction1 131 | if ncond1 then 132 | if ncond2 then 133 | naction2 134 | state <= S17; 135 | else 136 | state <= S18; 137 | end if; 138 | else 139 | end if; 140 | when S17 => 141 | when S18 => 142 | if ncond2 then 143 | naction2 144 | state <= S17; 145 | end if; 146 | end case; 147 | end if; 148 | end process; 149 | end architecture; 150 | -------------------------------------------------------------------------------- /tests/test_output/uart_receive_fsm.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: uart_receive_fsm.vhd 2 | -- Created by HDL-FSM-Editor 3 | library ieee; 4 | use ieee.std_logic_1164.all; 5 | use ieee.math_real.all; 6 | 7 | architecture fsm of uart_receive is 8 | type t_state is (start_bit, wait_for_enable, idle, stop_bit, parity_bit, data_bit); 9 | signal state : t_state; 10 | function get_counter_bit_number return natural is 11 | variable counter_load_value_1_max : natural; 12 | variable counter_load_value_2 : natural; 13 | variable max_load_value : natural; 14 | begin 15 | counter_load_value_1_max := 2**g_divisor_width - 1 + 2**(g_divisor_width-1) - 1 - 4; 16 | counter_load_value_2 := g_data_width; 17 | if counter_load_value_1_max>counter_load_value_2 then 18 | max_load_value := counter_load_value_1_max; 19 | else 20 | max_load_value := counter_load_value_2; 21 | end if; 22 | -- How many bits are needed for max_load_value? 23 | -- With n bits the biggest number is 2**n-1. 24 | -- max_load_value <= 2**n-1 25 | -- 2**n >= max_load_value + 1 26 | -- n >= ld(max_load_value + 1) True only as logarithmus dualis ld() is a monoton increasing function. 27 | -- As ieee.math_real.all only has a logarithmus function with base e, the calculation is: 28 | return integer(ceil(log(real(max_load_value+1))/log(2.0))); 29 | end function; 30 | signal counter : unsigned(get_counter_bit_number-1 downto 0); 31 | signal data : std_logic_vector(g_data_width-1 downto 0); 32 | signal parity_sum : std_logic; 33 | begin 34 | p_states: process (res_i, clk_i) 35 | begin 36 | if res_i='1' then 37 | state <= idle; 38 | enable_receive_clock_divider_o <= '0'; 39 | counter <= (others => '0'); 40 | data <= (others => '0'); 41 | parity_sum <= '0'; 42 | parity_err_o <= '0'; 43 | ready_receive_o <= '0'; 44 | elsif rising_edge(clk_i) then 45 | -- State Machine: 46 | case state is 47 | when start_bit => 48 | if counter=0 then 49 | enable_receive_clock_divider_o <= '1'; 50 | counter <= to_unsigned(g_data_width, counter'length); 51 | state <= wait_for_enable; 52 | else 53 | counter <= counter - 1; 54 | end if; 55 | when wait_for_enable => 56 | if receive_clock_enable_i='1' then 57 | data <= rx_sync_i & data(data'high downto 1); 58 | counter <= counter - 1; 59 | parity_sum <= parity_sum xor rx_sync_i; 60 | state <= data_bit; 61 | end if; 62 | when idle => 63 | if rx_sync_i='0' then 64 | -- receive_clock_enable_i must first be active in the middle 65 | -- of the first databit. 66 | -- The startbit has divisor_i clock-periods. 67 | -- The middle of the databit is after divisor_i/2 clock-periods. 68 | -- One clock period is lost at this transition. 69 | -- A second is lost at activating enable_receive_clock_divider_o. 70 | -- A third is lost at activating receive_clock_enable_i. 71 | -- A fourth is lost at sampling rx_sync_i. 72 | counter <= to_unsigned(to_integer(divisor_i)+to_integer(divisor_i)/2 - 4, counter'length); 73 | parity_sum <= '0'; 74 | parity_err_o <= '0'; 75 | state <= start_bit; 76 | end if; 77 | when stop_bit => 78 | enable_receive_clock_divider_o <= '0'; 79 | ready_receive_o <= '0'; 80 | state <= idle; 81 | when parity_bit => 82 | if receive_clock_enable_i='1' then 83 | if (parity_sum='1' and g_odd_parity=false) or 84 | (parity_sum='0' and g_odd_parity=true) then 85 | parity_err_o <= '1'; 86 | end if; 87 | ready_receive_o <= '1'; 88 | state <= stop_bit; 89 | end if; 90 | when data_bit => 91 | if receive_clock_enable_i='1' then 92 | if counter=0 then 93 | if g_has_parity=true then 94 | parity_sum <= parity_sum xor rx_sync_i; 95 | state <= parity_bit; 96 | else 97 | ready_receive_o <= '1'; 98 | state <= stop_bit; 99 | end if; 100 | else 101 | data <= rx_sync_i & data(data'high downto 1); 102 | counter <= counter - 1; 103 | parity_sum <= parity_sum xor rx_sync_i; 104 | end if; 105 | end if; 106 | end case; 107 | end if; 108 | end process; 109 | -- Global Actions combinatorial: 110 | data_o <= data; 111 | end architecture; 112 | -------------------------------------------------------------------------------- /src/dialogs/regex_dialog.py: -------------------------------------------------------------------------------- 1 | """ 2 | This class lets the user configure regex patterns for log parsing. 3 | """ 4 | 5 | import re 6 | import tkinter as tk 7 | from dataclasses import dataclass 8 | from tkinter import simpledialog, ttk 9 | 10 | 11 | @dataclass 12 | class RegexConfig: 13 | """Configuration for regex pattern matching in log files.""" 14 | 15 | pattern: str 16 | filename_group: str 17 | line_number_group: str 18 | debug_active: bool 19 | 20 | 21 | class RegexDialog(simpledialog.Dialog): 22 | def __init__( 23 | self, 24 | parent: tk.Tk | tk.Toplevel, 25 | language: str, 26 | current_pattern: str, 27 | current_filename_group: str, 28 | current_line_number_group: str, 29 | current_debug_active: bool, 30 | ) -> None: 31 | self.language = language 32 | self.current_pattern = current_pattern 33 | self.current_filename_group = current_filename_group 34 | self.current_line_number_group = current_line_number_group 35 | self.current_debug_active = current_debug_active 36 | super().__init__(parent, "Enter Regex for Python:") 37 | 38 | def validate_regex(self, pattern: str) -> bool: 39 | """Validate if the given pattern is a valid regex.""" 40 | if not pattern.strip(): 41 | return True # Empty pattern is considered valid 42 | try: 43 | re.compile(pattern) 44 | return True 45 | except re.error: 46 | return False 47 | 48 | def on_pattern_change(self, event: tk.Event) -> None: 49 | """Handle pattern entry changes and update background color based on validity.""" 50 | pattern = self.pattern_entry.get() 51 | is_valid = self.validate_regex(pattern) 52 | 53 | if is_valid: 54 | self.pattern_entry.configure(style="TEntry") 55 | else: 56 | # Create a custom style for invalid regex with light red background 57 | style = ttk.Style() 58 | style.configure("InvalidRegex.TEntry", fieldbackground="#ffcccc") 59 | self.pattern_entry.configure(style="InvalidRegex.TEntry") 60 | 61 | def body(self, master: tk.Frame) -> tk.Widget | None: 62 | """Create the dialog body. Return the widget that should have initial focus.""" 63 | # Header 64 | ttk.Label( 65 | master, 66 | text="Regex to extract file name and line number from simulator messages:", 67 | justify="left", 68 | ).grid(row=0, column=0, sticky="ew", padx=0, pady=0) 69 | 70 | # Pattern entry 71 | self.pattern_entry = ttk.Entry(master) 72 | self.pattern_entry.grid(row=1, column=0, sticky="ew", padx=0, pady=0) 73 | self.pattern_entry.insert(0, self.current_pattern) 74 | # Bind the validation function to key release events 75 | self.pattern_entry.bind("", self.on_pattern_change) 76 | # Initial validation 77 | self.on_pattern_change(None) 78 | 79 | # Group identifiers frame 80 | id_frame = ttk.Frame(master) 81 | id_frame.grid(row=2, column=0, sticky="ew", padx=0, pady=0) 82 | 83 | # Filename group 84 | ttk.Label(id_frame, text="Group identifier for file-name:", justify="left").grid( 85 | row=0, column=0, sticky="w", padx=0, pady=0 86 | ) 87 | self.filename_entry = ttk.Entry(id_frame, width=40) 88 | self.filename_entry.grid(row=0, column=1, sticky="ew", padx=5, pady=2) 89 | self.filename_entry.insert(0, self.current_filename_group) 90 | 91 | # Line number group 92 | ttk.Label(id_frame, text="Group identifier for line-number:", justify="left").grid( 93 | row=1, column=0, sticky="w", padx=0, pady=0 94 | ) 95 | self.line_number_entry = ttk.Entry(id_frame, width=40) 96 | self.line_number_entry.grid(row=1, column=1, sticky="ew", padx=5, pady=2) 97 | self.line_number_entry.insert(0, self.current_line_number_group) 98 | 99 | # Debug options frame 100 | debug_frame = ttk.Frame(master) 101 | debug_frame.grid(row=3, column=0, sticky="ew", padx=0, pady=2) 102 | ttk.Label(debug_frame, text="Debug Regex at STDOUT:", padding=0).grid(row=0, column=0, sticky="w") 103 | self.debug_var = tk.IntVar(value=2 if self.current_debug_active else 1) 104 | ttk.Radiobutton(debug_frame, takefocus=False, variable=self.debug_var, text="Inactive", value=1).grid( 105 | row=0, column=1, sticky="w" 106 | ) 107 | ttk.Radiobutton(debug_frame, takefocus=False, variable=self.debug_var, text="Active", value=2).grid( 108 | row=0, column=2, sticky="w" 109 | ) 110 | 111 | # Configure grid weights 112 | master.columnconfigure(0, weight=1) 113 | id_frame.columnconfigure(1, weight=1) 114 | 115 | return self.pattern_entry # Return widget that should have initial focus 116 | 117 | def apply(self) -> None: 118 | """Process the dialog data and set the result.""" 119 | self.result = RegexConfig( 120 | pattern=self.pattern_entry.get(), 121 | filename_group=self.filename_entry.get(), 122 | line_number_group=self.line_number_entry.get(), 123 | debug_active=self.debug_var.get() == 2, 124 | ) 125 | 126 | @classmethod 127 | def ask_regex( 128 | cls, 129 | parent: tk.Tk | tk.Toplevel, 130 | language: str, 131 | current_pattern: str, 132 | current_filename_group: str, 133 | current_line_number_group: str, 134 | current_debug_active: bool, 135 | ) -> RegexConfig | None: 136 | """Show the regex configuration dialog and return the result.""" 137 | dialog = cls( 138 | parent, language, current_pattern, current_filename_group, current_line_number_group, current_debug_active 139 | ) 140 | return dialog.result 141 | -------------------------------------------------------------------------------- /src/update_hdl_tab.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copies the generated HDL into the HDL-tab if the HDL is younger than the hfe-file. 3 | """ 4 | 5 | import os 6 | import tkinter as tk 7 | from tkinter import messagebox 8 | 9 | import codegen.hdl_generation as hdl_generation 10 | import main_window 11 | 12 | 13 | class UpdateHdlTab: 14 | """ 15 | Copies the generated HDL into the HDL-tab if the HDL is younger than the hfe-file. 16 | Not called in batch mode. 17 | """ 18 | 19 | def __init__(self, language, number_of_files, readfile, generate_path, module_name) -> None: 20 | self.date_of_hdl_file = 0.0 # Default-Value, used when hdl-file not exists. 21 | self.date_of_hdl_file2 = 0.0 # Default-Value, used when hdl-file not exists. 22 | if language == "VHDL": 23 | if number_of_files == 1: 24 | hdlfilename = generate_path + "/" + module_name + ".vhd" 25 | hdlfilename_architecture = None 26 | else: 27 | hdlfilename = generate_path + "/" + module_name + "_e.vhd" 28 | hdlfilename_architecture = generate_path + "/" + module_name + "_fsm.vhd" 29 | else: # verilog 30 | hdlfilename = generate_path + "/" + module_name + ".v" 31 | hdlfilename_architecture = None 32 | # Compare modification time of HDL file against modification_time of design file (.hse): 33 | main_window.hdl_frame_text.config(state=tk.NORMAL) 34 | main_window.hdl_frame_text.delete("1.0", tk.END) 35 | main_window.hdl_frame_text.insert("1.0", "") 36 | main_window.hdl_frame_text.config(state=tk.DISABLED) 37 | hdl = "" 38 | if self.__hdl_is_up_to_date(readfile, hdlfilename, hdlfilename_architecture, show_message=False): 39 | # print("HDL-file exists and is 'newer' than the design-file =", self.date_of_hdl_file) 40 | try: 41 | with open(hdlfilename, encoding="utf-8") as fileobject: 42 | entity = fileobject.read() 43 | hdl += self.__add_line_numbers(entity) 44 | except FileNotFoundError: 45 | messagebox.showerror( 46 | "Error in HDL-FSM-Editor", "File " + hdlfilename + " could not be opened for copying into HDL-Tab." 47 | ) 48 | if hdlfilename_architecture is not None: 49 | # HDL-file exists and was generated after the design-file was saved. 50 | try: 51 | with open(hdlfilename_architecture, encoding="utf-8") as fileobject: 52 | arch = fileobject.read() 53 | hdl += self.__add_line_numbers(arch) 54 | except FileNotFoundError: 55 | messagebox.showerror( 56 | "Error in HDL-FSM-Editor", 57 | "File " 58 | + hdlfilename_architecture 59 | + " (architecture-file) could not be opened for copying into HDL-Tab.", 60 | ) 61 | # Create hdl without writing to file for Link-Generation: 62 | hdl_generation.run_hdl_generation(write_to_file=False, is_script_mode=False) 63 | main_window.hdl_frame_text.config(state=tk.NORMAL) 64 | main_window.hdl_frame_text.insert("1.0", hdl) 65 | main_window.hdl_frame_text.config(state=tk.DISABLED) 66 | main_window.hdl_frame_text.update_highlight_tags( 67 | 10, ["not_read", "not_written", "control", "datatype", "function", "comment"] 68 | ) 69 | 70 | def __hdl_is_up_to_date(self, path_name, hdlfilename, hdlfilename_architecture, show_message) -> bool: 71 | if not os.path.isfile(path_name): 72 | messagebox.showerror( 73 | "Error in HDL-FSM-Editor", "The HDL-FSM-Editor project file " + path_name + " is missing." 74 | ) 75 | return False 76 | if not os.path.isfile(hdlfilename): 77 | if show_message: 78 | messagebox.showerror("Error in HDL-FSM-Editor", "The file " + hdlfilename + " is missing.") 79 | return False 80 | if hdlfilename_architecture is not None and not os.path.isfile(hdlfilename_architecture): 81 | if show_message: 82 | messagebox.showerror( 83 | "Error in HDL-FSM-Editor", 84 | "The entity-file exists, but the architecture file\n" + hdlfilename_architecture + " is missing.", 85 | ) 86 | return False 87 | self.date_of_hdl_file = os.path.getmtime(hdlfilename) 88 | if hdlfilename_architecture is not None: 89 | self.date_of_hdl_file2 = os.path.getmtime(hdlfilename_architecture) 90 | if self.date_of_hdl_file < os.path.getmtime(path_name): 91 | if show_message: 92 | messagebox.showerror( 93 | "Error in HDL-FSM-Editor", 94 | "The file\n" + hdlfilename + "\nis older than\n" + path_name + "\nPlease generate HDL again.", 95 | ) 96 | return False 97 | if hdlfilename_architecture is not None and self.date_of_hdl_file2 < os.path.getmtime(path_name): 98 | if show_message: 99 | messagebox.showerror( 100 | "Error in HDL-FSM-Editor", 101 | "The architecture file\n" 102 | + hdlfilename_architecture 103 | + "\nis older than\n" 104 | + path_name 105 | + "\nPlease generate HDL again.", 106 | ) 107 | return False 108 | return True 109 | 110 | def __add_line_numbers(self, text) -> str: 111 | text_lines = text.split("\n") 112 | text_length_as_string = str(len(text_lines)) 113 | number_of_needed_digits_as_string = str(len(text_length_as_string)) 114 | content_with_numbers = "" 115 | for line_number, line in enumerate(text_lines, start=1): 116 | content_with_numbers += ( 117 | format(line_number, "0" + number_of_needed_digits_as_string + "d") + ": " + line + "\n" 118 | ) 119 | return content_with_numbers 120 | 121 | def get_date_of_hdl_file(self) -> float: 122 | return self.date_of_hdl_file 123 | 124 | def get_date_of_hdl_file2(self) -> float: 125 | return self.date_of_hdl_file2 126 | -------------------------------------------------------------------------------- /src/linting.py: -------------------------------------------------------------------------------- 1 | """ 2 | Methods needed for highlighting signals, which are not read, not written, not defined 3 | """ 4 | 5 | import canvas_editing 6 | import custom_text 7 | import main_window 8 | 9 | 10 | def recreate_keyword_list_of_unused_signals() -> None: 11 | main_window.keywords["not_read"].clear() 12 | main_window.keywords["not_written"].clear() 13 | 14 | variables_to_write = _get_all_read_variables() 15 | variables_to_read = _get_all_written_variables() 16 | variables_to_write = _detect_not_read_input_ports(variables_to_write) 17 | variables_to_read, variables_to_write = _detect_not_written_output_ports(variables_to_read, variables_to_write) 18 | variables_to_read, variables_to_write = _detect_not_written_not_read_signals(variables_to_read, variables_to_write) 19 | variables_to_read, variables_to_write = _detect_not_written_constants(variables_to_read, variables_to_write) 20 | variables_to_write = _remove_port_types(variables_to_write) 21 | variables_to_read, variables_to_write = _remove_generics(variables_to_read, variables_to_write) 22 | main_window.keywords["not_written"] += variables_to_write 23 | main_window.keywords["not_read"] += variables_to_read 24 | 25 | 26 | def _get_all_read_variables(): 27 | variables_to_write = [] 28 | for _, read_variables_of_window in custom_text.CustomText.read_variables_of_all_windows.items(): 29 | variables_to_write += read_variables_of_window 30 | variables_to_write = list(set(variables_to_write)) # remove duplicates 31 | # print("variables_to_write =", variables_to_write) 32 | return variables_to_write 33 | 34 | 35 | def _get_all_written_variables(): 36 | variables_to_read = [] 37 | for _, written_variables_of_window in custom_text.CustomText.written_variables_of_all_windows.items(): 38 | variables_to_read += written_variables_of_window 39 | variables_to_read = list(set(variables_to_read)) # remove duplicates 40 | # print("variables_to_read =", variables_to_read) 41 | return variables_to_read 42 | 43 | 44 | def _detect_not_read_input_ports(variables_to_write): 45 | for input_port in main_window.interface_ports_text.readable_ports_list: 46 | if input_port not in variables_to_write: 47 | if input_port != main_window.clock_signal_name.get(): 48 | main_window.keywords["not_read"].append(input_port) 49 | else: 50 | # Inputs must not be written: 51 | variables_to_write.remove(input_port) 52 | return variables_to_write 53 | 54 | 55 | def _detect_not_written_output_ports(variables_to_read, variables_to_write): 56 | for output in main_window.interface_ports_text.writable_ports_list: 57 | if output not in variables_to_read: 58 | main_window.keywords["not_written"].append(output) 59 | else: 60 | # Outputs must not be read: 61 | variables_to_read.remove(output) 62 | if main_window.language.get() != "VHDL" and output in variables_to_write: # A Verilog output is read. 63 | # Writing of outputs is checked by the variables_to_read list: 64 | variables_to_write.remove(output) 65 | return variables_to_read, variables_to_write 66 | 67 | 68 | def _detect_not_written_not_read_signals(variables_to_read, variables_to_write): 69 | # Check if each signal or variable is written and is read: 70 | for signal in ( 71 | main_window.internals_architecture_text.signals_list 72 | + main_window.internals_process_combinatorial_text.signals_list 73 | + main_window.internals_process_clocked_text.signals_list 74 | ): 75 | if signal in variables_to_read and signal in variables_to_write: 76 | variables_to_read.remove(signal) 77 | variables_to_write.remove(signal) 78 | elif signal in variables_to_read: 79 | main_window.keywords["not_read"].append(signal) 80 | else: 81 | main_window.keywords["not_written"].append(signal) 82 | return variables_to_read, variables_to_write 83 | 84 | 85 | def _detect_not_written_constants(variables_to_read, variables_to_write): 86 | for constant in ( 87 | main_window.internals_architecture_text.constants_list 88 | + main_window.internals_process_combinatorial_text.constants_list 89 | + main_window.internals_process_clocked_text.constants_list 90 | ): 91 | # variables_to_read = [value for value in variables_to_read if value != constant] # remove constant from list 92 | if constant in variables_to_read: 93 | variables_to_read.remove(constant) 94 | if constant not in variables_to_write: # Then the constant is not read. 95 | main_window.keywords["not_read"].append(constant) 96 | else: 97 | variables_to_write.remove(constant) 98 | return variables_to_read, variables_to_write 99 | 100 | 101 | def _remove_port_types(variables_to_write): 102 | for port_type in main_window.interface_ports_text.port_types_list: 103 | if port_type in variables_to_write: 104 | variables_to_write.remove(port_type) 105 | return variables_to_write 106 | 107 | 108 | def _remove_generics(variables_to_read, variables_to_write): 109 | for generic in main_window.interface_generics_text.generics_list: 110 | if generic in variables_to_read: 111 | variables_to_read.remove(generic) 112 | if generic in variables_to_write: 113 | variables_to_write.remove(generic) 114 | return variables_to_read, variables_to_write 115 | 116 | 117 | def update_highlight_tags_in_all_windows_for_not_read_not_written_and_comment() -> None: 118 | # Comment must be the last, because in the range of a comment all other tags are deleted: 119 | for text_id in custom_text.CustomText.read_variables_of_all_windows: 120 | text_id.update_highlight_tags(canvas_editing.fontsize, ["not_read", "not_written", "comment"]) 121 | text_ids_fixed = [ 122 | main_window.interface_generics_text, 123 | main_window.interface_package_text, 124 | main_window.interface_ports_text, 125 | main_window.internals_architecture_text, 126 | main_window.internals_process_clocked_text, 127 | main_window.internals_process_combinatorial_text, 128 | main_window.internals_package_text, 129 | ] 130 | for text_id in text_ids_fixed: 131 | text_id.update_highlight_tags(10, ["not_read", "not_written", "comment"]) 132 | -------------------------------------------------------------------------------- /src/link_dictionary.py: -------------------------------------------------------------------------------- 1 | """ 2 | The LinkDictionary shall carry all information which are needed to create hyperlinks from each line in the generated HDL 3 | to the graphical source of the line. 4 | The LinkDictionary is created once, when the HDL-SCHEM-Editor is started. 5 | All accesses to the LinkDictionary are done by using the class variable link_dict_reference. 6 | The LinkDictionary is filled when the HDL is generated and at some other events. 7 | At HDL generation only the needed information is gathered and send to the LinkDictionary. 8 | The LinkDictionary must then build up the dictionary which afterwards will be used by the HDL tab. 9 | The HDL tab observes the mouse movements and at a left mouse button press at a line a method of the 10 | LinkDictionary is called, which shows the graphical source of this HLD code line. 11 | 12 | When the LinkDictionary is filled by the HDL generation, a HDL-file-name and a HDL-file-line-number must be handed over. 13 | These 2 parameters are the keys of the LinkDictionary, so when the user clicks on a line in a HDL file in the HDL-tab, 14 | line-number and file-name are determined and the corresponding entry of the LinkDictionary can be read. 15 | """ 16 | 17 | import tkinter as tk 18 | 19 | import codegen.hdl_generation as hdl_generation 20 | import main_window 21 | from codegen.hdl_generation_config import GenerationConfig 22 | from constants import GuiTab 23 | 24 | 25 | class LinkDictionary: 26 | """ 27 | The LinkDictionary shall carry all information which are needed to create hyperlinks from each line in 28 | the generated HDL to the graphical source of the line. 29 | """ 30 | 31 | def __init__(self, root) -> None: 32 | self.root = root 33 | self.link_dict = {} 34 | 35 | def add( 36 | self, 37 | file_name: str, # Filename in which the HDL-item is stored 38 | file_line_number: int, # File-line-number in which the HDL-item is stored 39 | hdl_item_type: str, # One of HdlItemType enum values 40 | number_of_lines: int, # How many lines does the HDL-item use in the file 41 | hdl_item_name: str | tk.Widget, # String when "Control-Tab", widget-references in all other cases 42 | ) -> None: 43 | # print("add =", file_name, file_line_number, hdl_item_type, number_of_lines, hdl_item_name) 44 | if file_name not in self.link_dict: 45 | self.link_dict[file_name] = {} 46 | if hdl_item_type == "Control-Tab": 47 | self.link_dict[file_name][file_line_number] = { 48 | "tab_name": GuiTab.CONTROL, 49 | "widget_reference": main_window, 50 | "hdl_item_type": hdl_item_name, 51 | "object_identifier": "", 52 | "number_of_line": "", 53 | } 54 | elif hdl_item_type == "custom_text_in_interface_tab": 55 | for text_line_number in range(1, number_of_lines + 1): 56 | self.link_dict[file_name][file_line_number + text_line_number - 1] = { 57 | "tab_name": GuiTab.INTERFACE, 58 | "widget_reference": hdl_item_name, 59 | "hdl_item_type": "", 60 | "object_identifier": "", 61 | "number_of_line": text_line_number, 62 | } 63 | elif hdl_item_type == "custom_text_in_internals_tab": 64 | for text_line_number in range(1, number_of_lines + 1): 65 | self.link_dict[file_name][file_line_number + text_line_number - 1] = { 66 | "tab_name": GuiTab.INTERNALS, 67 | "widget_reference": hdl_item_name, 68 | "hdl_item_type": "", 69 | "object_identifier": "", 70 | "number_of_line": text_line_number, 71 | } 72 | elif hdl_item_type == "custom_text_in_diagram_tab": 73 | for text_line_number in range(1, number_of_lines + 1): 74 | self.link_dict[file_name][file_line_number + text_line_number - 1] = { 75 | "tab_name": GuiTab.DIAGRAM, 76 | "widget_reference": hdl_item_name, 77 | "hdl_item_type": "", 78 | "object_identifier": "", 79 | "number_of_line": text_line_number, 80 | } 81 | 82 | def has_link(self, file_name: str, file_line_number: int) -> bool: 83 | """Check if a link exists for the given file and line.""" 84 | return file_name in self.link_dict and file_line_number in self.link_dict[file_name] 85 | 86 | def jump_to_source(self, selected_file, file_line_number) -> None: 87 | # print("jump_to_source", selected_file, file_line_number) 88 | tab_to_show = self.link_dict[selected_file][file_line_number]["tab_name"] 89 | widget = self.link_dict[selected_file][file_line_number]["widget_reference"] 90 | hdl_item_type = self.link_dict[selected_file][file_line_number]["hdl_item_type"] 91 | object_identifier = self.link_dict[selected_file][file_line_number]["object_identifier"] 92 | number_of_line = self.link_dict[selected_file][file_line_number]["number_of_line"] 93 | main_window.show_tab(tab_to_show) 94 | widget.highlight_item(hdl_item_type, object_identifier, number_of_line) 95 | 96 | def jump_to_hdl(self, selected_file, file_line_number) -> None: 97 | if main_window.select_file_number_text.get() == 2: 98 | gen_config = GenerationConfig.from_main_window() 99 | file_name_architecture = gen_config.get_architecture_file() 100 | if file_name_architecture and selected_file == file_name_architecture: 101 | file_line_number += hdl_generation.last_line_number_of_file1 102 | main_window.show_tab(GuiTab.GENERATED_HDL) 103 | main_window.hdl_frame_text.highlight_item("", "", file_line_number) 104 | main_window.hdl_frame_text.config(state="normal") 105 | main_window.hdl_frame_text.focus_set() 106 | main_window.hdl_frame_text.config(state="disabled") 107 | 108 | def clear_link_dict(self, file_name) -> None: 109 | if file_name in self.link_dict: 110 | # print("clear_link_dict: file_name =", file_name) 111 | self.link_dict.pop(file_name) 112 | 113 | 114 | _link_dictionary: LinkDictionary | None = None 115 | 116 | 117 | def init_link_dict(root) -> None: 118 | global _link_dictionary 119 | _link_dictionary = LinkDictionary(root) 120 | 121 | 122 | def link_dict() -> LinkDictionary: 123 | assert _link_dictionary is not None 124 | return _link_dictionary 125 | -------------------------------------------------------------------------------- /src/write_data_creator.py: -------------------------------------------------------------------------------- 1 | """ 2 | This class modifies the data at file-write in a way 3 | that the resulting file will only differ from the read file 4 | if the user added/removed/moved any schematic-element or 5 | changed any text/name/contol-information. Any scrolling, zooming 6 | will not create a different file content. 7 | The "include timestamp in generated HDL" must be switched off if 8 | the file shall not change. 9 | """ 10 | 11 | import canvas_editing 12 | 13 | 14 | class WriteDataCreator: 15 | def __init__(self, standard_size) -> None: 16 | self.standard_size = standard_size 17 | self.last_design_dictionary = None 18 | 19 | def store_as_compare_object(self, design_dictionary) -> None: 20 | self.last_design_dictionary = design_dictionary 21 | 22 | def zoom_graphic_to_standard_size(self, actual_size) -> float: 23 | zoom_factor = self.standard_size / actual_size 24 | canvas_editing.canvas_zoom([0, 0], zoom_factor) 25 | return zoom_factor 26 | 27 | def zoom_graphic_back_to_actual_size(self, zoom_factor) -> None: 28 | canvas_editing.canvas_zoom([0, 0], 1 / zoom_factor) 29 | return 30 | 31 | def round_and_sort_data(self, design_dictionary, all_graphical_elements) -> dict[str, list]: 32 | used_graphic_elements = self._create_used_graphic_elements(design_dictionary, all_graphical_elements) 33 | design_dictionary = self._sort_graphic_elements(design_dictionary, used_graphic_elements) 34 | design_dictionary = self._round_coordinates(design_dictionary, used_graphic_elements) 35 | design_dictionary = self._round_parameters(design_dictionary) 36 | self.store_as_compare_object(design_dictionary) 37 | return design_dictionary 38 | 39 | def _create_used_graphic_elements(self, design_dictionary, all_graphical_elements) -> list: 40 | used_graphic_elements = [] 41 | for graphical_element in all_graphical_elements: 42 | if graphical_element in design_dictionary: 43 | used_graphic_elements.append(graphical_element) 44 | return used_graphic_elements 45 | 46 | def _round_coordinates(self, design_dictionary, used_graphic_elements) -> dict[str, list]: 47 | for graphical_element in used_graphic_elements: 48 | if graphical_element in ("window_condition_action_block", "window_global_actions"): 49 | index_of_tags = 3 50 | elif graphical_element.startswith("window_"): 51 | index_of_tags = 2 52 | else: 53 | index_of_tags = 1 54 | for graphic_instance_index, graphical_instance_property_list in enumerate( 55 | design_dictionary[graphical_element] 56 | ): 57 | identifier_at_write = graphical_instance_property_list[index_of_tags][0] 58 | coordinates_at_write = graphical_instance_property_list[0] 59 | identifier_from_last, coordinates_from_last = self._get_info_from_last_data_by_index( 60 | graphical_element, graphic_instance_index, index_of_tags 61 | ) 62 | for coordinate_index, coordinate_at_write in enumerate(coordinates_at_write): 63 | coordinate_at_write = round(coordinate_at_write, 0) # round to integer 64 | if ( 65 | # In the last data there is a corresponding graphical instance: 66 | identifier_from_last != "" 67 | # The graphic instance is not a wire or a wire whose number of points did not change: 68 | and len(coordinates_at_write) == len(coordinates_from_last) 69 | # The graphic instance exists in last data at same position in the list: 70 | and identifier_at_write == identifier_from_last 71 | # The last coordinate is already a (rounded) integer value: 72 | and coordinates_from_last[coordinate_index] % 1 == 0 73 | # And differs from the new value: 74 | and coordinates_from_last[coordinate_index] != coordinate_at_write 75 | # And differs only by a small amount: 76 | and abs(coordinates_from_last[coordinate_index] - coordinate_at_write) 77 | < 0.2 * self.standard_size 78 | ): 79 | coordinate_at_write = coordinates_from_last[coordinate_index] 80 | graphical_instance_property_list[0][coordinate_index] = coordinate_at_write 81 | return design_dictionary 82 | 83 | def _get_info_from_last_data_by_index(self, graphical_element, graphic_instance_index, index_of_tags) -> list: 84 | if ( 85 | self.last_design_dictionary is not None 86 | and graphical_element in self.last_design_dictionary 87 | and graphic_instance_index < len(self.last_design_dictionary[graphical_element]) 88 | ): 89 | identifier_from_last = self.last_design_dictionary[graphical_element][graphic_instance_index][ 90 | index_of_tags 91 | ][0] 92 | coordinates_from_last = self.last_design_dictionary[graphical_element][graphic_instance_index][0] 93 | else: 94 | identifier_from_last = "" 95 | coordinates_from_last = [] 96 | return identifier_from_last, coordinates_from_last 97 | 98 | def _round_parameters(self, design_dictionary) -> dict[str, list]: 99 | design_dictionary["label_fontsize"] = round(design_dictionary["label_fontsize"], 0) 100 | design_dictionary["priority_distance"] = round(design_dictionary["priority_distance"], 0) 101 | return design_dictionary 102 | 103 | def _sort_graphic_elements(self, design_dictionary, used_graphic_elements) -> dict[str, list]: 104 | # At all sorts the key is the first tag which the graphical element has (identifier tag). 105 | # The sorting will always give the same result if the order of tags is not changed by tkinter. 106 | for graphical_element in used_graphic_elements: 107 | if graphical_element in ("window_condition_action_block", "window_global_actions"): 108 | index_of_tags = 3 109 | elif graphical_element.startswith("window_"): 110 | index_of_tags = 2 111 | else: 112 | index_of_tags = 1 113 | design_dictionary[graphical_element] = sorted( 114 | design_dictionary[graphical_element], key=lambda x: x[index_of_tags][0] 115 | ) 116 | return design_dictionary 117 | -------------------------------------------------------------------------------- /tests/test_golden_file_generation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Golden file tests for HDL-FSM-Editor. 3 | """ 4 | 5 | import json 6 | import os 7 | import re 8 | import subprocess 9 | import sys 10 | from pathlib import Path 11 | from typing import Any 12 | 13 | import pytest 14 | 15 | # This provides additional parameters for the test. 16 | # Files have to be specified here only if they are non-standard. 17 | # - Which stdout is expected. (to match against warnings / generation errors). 18 | # - If the generation is expected to succeed. 19 | TEST_CONFIGURATION = { 20 | "fifo_test_error.hfe": { 21 | "generation_should_succeed": False, 22 | "validation_patterns": [ 23 | "A transition starting at state S1\nwith no condition hides a transition with lower priority.", 24 | ], 25 | }, 26 | } 27 | 28 | 29 | def read_hfe_metadata(hfe_file: Path) -> dict[str, Any]: 30 | """Read language and module name from HFE file.""" 31 | with open(hfe_file, encoding="utf-8") as f: 32 | data = json.load(f) 33 | return { 34 | "language": data.get("language", "VHDL"), 35 | "module_name": data.get("modulename", ""), 36 | "number_of_files": data.get("number_of_files", 1), 37 | } 38 | 39 | 40 | def get_output_file_names(hfe_file: Path, output_dir: Path) -> list[Path]: 41 | """Determine output file names based on HFE metadata.""" 42 | metadata = read_hfe_metadata(hfe_file) 43 | module_name = metadata["module_name"] 44 | language = metadata["language"] 45 | number_of_files = metadata["number_of_files"] 46 | 47 | if language == "VHDL": 48 | if number_of_files == 1: 49 | return [output_dir / f"{module_name}.vhd"] 50 | else: 51 | return [output_dir / f"{module_name}_e.vhd", output_dir / f"{module_name}_fsm.vhd"] 52 | elif language == "Verilog": 53 | return [output_dir / f"{module_name}.v"] 54 | elif language == "SystemVerilog": 55 | return [output_dir / f"{module_name}.sv"] 56 | else: 57 | raise ValueError(f"Unsupported language: {language}") 58 | 59 | 60 | def run_hdl_generation(hfe_file: Path, output_dir: Path) -> subprocess.CompletedProcess: 61 | """Run HDL-FSM-Editor to generate HDL from .hfe file.""" 62 | # Note: we have to ensure that the tests don't overwrite files of other tests. 63 | # This is currently not checked. So ensure that all modules have a unique module name! 64 | output_dir.mkdir(parents=True, exist_ok=True) 65 | original_cwd = os.getcwd() 66 | os.chdir(output_dir) 67 | 68 | try: 69 | cmd = [ 70 | sys.executable, 71 | str(Path(__file__).parent.parent / "src" / "main.py"), 72 | str(hfe_file), 73 | "--generate-hdl", 74 | "--no-version-check", 75 | "--no-message", 76 | ] 77 | return subprocess.run(cmd, capture_output=True, text=True, timeout=30) 78 | finally: 79 | os.chdir(original_cwd) 80 | 81 | 82 | def collect_hfe_test_cases() -> list[tuple[str, Path]]: 83 | """Collect all HFE files and create test cases.""" 84 | examples_dir = Path(__file__).parent / "test_input" 85 | hfe_files = list(examples_dir.glob("*.hfe")) 86 | if not hfe_files: 87 | pytest.skip("No .hfe files found in examples directory") 88 | 89 | return [(hfe_file.stem, hfe_file) for hfe_file in hfe_files] 90 | 91 | 92 | @pytest.mark.golden_file 93 | @pytest.mark.parametrize("test_id, hfe_file", collect_hfe_test_cases()) 94 | def test_golden_file_generation(test_id: str, hfe_file: Path, test_output_dir: Path): 95 | """Test HDL generation for individual HFE files. 96 | 97 | Handles both successful generation and expected validation failures. 98 | """ 99 | print(f"\nTesting {hfe_file.name}") 100 | 101 | # Get test configuration 102 | config = TEST_CONFIGURATION.get(hfe_file.name) 103 | should_succeed = config is None or config.get("generation_should_succeed", True) 104 | validation_patterns = config.get("validation_patterns", []) if config else [] 105 | 106 | print(f" Expected success: {should_succeed}") 107 | if validation_patterns: 108 | print(f" Expected validation patterns: {validation_patterns}") 109 | 110 | # Get expected output files 111 | metadata = read_hfe_metadata(hfe_file) 112 | output_files = get_output_file_names(hfe_file, test_output_dir) 113 | 114 | print(f" Language: {metadata['language']}") 115 | print(f" Module: {metadata['module_name']}") 116 | print(f" Output files: {[f.name for f in output_files]}") 117 | 118 | # Record modification times before generation 119 | before_times = { 120 | output_file: output_file.stat().st_mtime if output_file.exists() else 0 for output_file in output_files 121 | } 122 | 123 | # Generate HDL 124 | result = run_hdl_generation(hfe_file, test_output_dir) 125 | 126 | # Check output patterns regardless of success/failure 127 | if validation_patterns: 128 | output_text = result.stdout + result.stderr 129 | for pattern in validation_patterns: 130 | match = re.search(re.escape(pattern), output_text, re.IGNORECASE) 131 | assert match is not None, f"Expected validation pattern '{pattern}' not found in output:\n{output_text}" 132 | print(f" ✓ Found validation pattern (regex): {pattern}") 133 | 134 | if should_succeed: 135 | # Test successful generation 136 | assert result.returncode == 0, f"Generation failed: {result.stderr}" 137 | 138 | # Check each output file was created and modified 139 | for output_file in output_files: 140 | assert output_file.exists(), f"Output file not generated: {output_file}" 141 | 142 | after_time = output_file.stat().st_mtime 143 | was_modified = after_time > before_times[output_file] 144 | 145 | print(f" {output_file.name}: modified = {was_modified}") 146 | assert was_modified, f"File {output_file.name} was not modified during generation" 147 | 148 | else: 149 | # Test expected validation failure 150 | assert result.returncode != 0, "Expected validation failure but generation succeeded" 151 | 152 | # Verify no output files were modified 153 | for output_file in output_files: 154 | if output_file.exists(): 155 | after_time = output_file.stat().st_mtime 156 | was_modified = after_time > before_times[output_file] 157 | 158 | if was_modified: 159 | print(f" ⚠️ Warning: {output_file.name} was modified despite validation failure") 160 | 161 | 162 | if __name__ == "__main__": 163 | pytest.main([__file__]) 164 | -------------------------------------------------------------------------------- /src/reset_entry_handling.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains all methods needed for the reset-entry object. 3 | """ 4 | 5 | import tkinter as tk 6 | 7 | import canvas_editing 8 | import canvas_modify_bindings 9 | import main_window 10 | import undo_handling 11 | 12 | reset_entry_number = 0 13 | _difference_x = 0 14 | _difference_y = 0 15 | 16 | 17 | def insert_reset_entry(event) -> None: 18 | global reset_entry_number 19 | if reset_entry_number == 0: # Only 1 reset entry is allowed. 20 | reset_entry_number += 1 21 | main_window.reset_entry_button.config(state=tk.DISABLED) 22 | _insert_reset_entry_in_canvas(event) 23 | undo_handling.design_has_changed() 24 | canvas_modify_bindings.switch_to_move_mode() 25 | 26 | 27 | def _insert_reset_entry_in_canvas(event) -> None: 28 | canvas_grid_coordinates_of_the_event = ( 29 | canvas_editing.translate_window_event_coordinates_in_rounded_canvas_coordinates(event) 30 | ) 31 | _create_reset_entry(canvas_grid_coordinates_of_the_event) 32 | 33 | 34 | def _create_reset_entry(canvas_grid_coordinates_of_the_event) -> None: 35 | reset_entry_polygon = _create_polygon_shape_for_reset_entry() 36 | reset_entry_polygon = _move_reset_entry_polygon_to_event(canvas_grid_coordinates_of_the_event, reset_entry_polygon) 37 | draw_reset_entry(reset_entry_polygon, "reset_entry") 38 | draw_reset_entry_text( 39 | canvas_grid_coordinates_of_the_event[0] - 4 * canvas_editing.reset_entry_size / 5, 40 | canvas_grid_coordinates_of_the_event[1], 41 | text="Reset", 42 | tag="reset_text", 43 | ) 44 | 45 | 46 | def draw_reset_entry(reset_entry_polygon, tag): 47 | polygon_id = main_window.canvas.create_polygon(*reset_entry_polygon, fill="red", outline="orange", tag=tag) 48 | main_window.canvas.tag_bind( 49 | polygon_id, "", lambda event, id=polygon_id: main_window.canvas.itemconfig(id, width=2) 50 | ) 51 | main_window.canvas.tag_bind( 52 | polygon_id, "", lambda event, id=polygon_id: main_window.canvas.itemconfig(id, width=1) 53 | ) 54 | 55 | 56 | def draw_reset_entry_text(coord_x, coord_y, text, tag): 57 | main_window.canvas.create_text( 58 | coord_x, 59 | coord_y, 60 | text=text, 61 | tag=tag, 62 | font=canvas_editing.state_name_font, 63 | ) 64 | 65 | 66 | def _create_polygon_shape_for_reset_entry() -> list[list]: 67 | # upper_left_corner = [-20,-12] 68 | # upper_right_corner = [+20,-12] 69 | # point_corner = [+32, 0] connect-point for transition 70 | # lower_right_corner = [+20,+12] 71 | # lower_left_corner = [-20,+12] 72 | size = canvas_editing.reset_entry_size 73 | # Coordinates when the mouse-pointer is at point_corner of the polygon: 74 | upper_left_corner = [-size / 2 - 4 * size / 5, -3 * size / 10] 75 | upper_right_corner = [+size / 2 - 4 * size / 5, -3 * size / 10] 76 | point_corner = [0, 0] 77 | lower_right_corner = [+size / 2 - 4 * size / 5, +3 * size / 10] 78 | lower_left_corner = [-size / 2 - 4 * size / 5, +3 * size / 10] 79 | return [upper_left_corner, upper_right_corner, point_corner, lower_right_corner, lower_left_corner] 80 | 81 | 82 | def _move_reset_entry_polygon_to_event(canvas_grid_coordinates_of_the_event, reset_entry_polygon): 83 | for p in reset_entry_polygon: 84 | p[0] += canvas_grid_coordinates_of_the_event[0] 85 | p[1] += canvas_grid_coordinates_of_the_event[1] 86 | return reset_entry_polygon 87 | 88 | 89 | def move_to(event_x, event_y, polygon_id, first, last) -> None: 90 | global _difference_x, _difference_y 91 | if first is True: 92 | # Calculate the difference between the "anchor" point and the event: 93 | coords = main_window.canvas.coords(polygon_id) 94 | middle_point = [coords[4], coords[5]] 95 | _difference_x, _difference_y = -event_x + middle_point[0], -event_y + middle_point[1] 96 | # Keep the distance between event and anchor point constant: 97 | event_x, event_y = event_x + _difference_x, event_y + _difference_y 98 | if last is True: 99 | event_x = canvas_editing.state_radius * round(event_x / canvas_editing.state_radius) 100 | event_y = canvas_editing.state_radius * round(event_y / canvas_editing.state_radius) 101 | width = _determine_width_of_the_polygon(polygon_id) 102 | height = _determine_height_of_the_polygon(polygon_id) 103 | new_upper_left_corner = _calculate_new_upper_left_corner_of_the_polygon(event_x, event_y, width, height) 104 | new_upper_right_corner = _calculate_new_upper_right_corner_of_the_polygon(event_x, event_y, width, height) 105 | new_point_right_corner = [event_x, event_y] 106 | new_lower_right_corner = _calculate_new_lower_right_corner_of_the_polygon(event_x, event_y, width, height) 107 | new_lower_left_corner = _calculate_new_lower_left_corner_of_the_polygon(event_x, event_y, width, height) 108 | new_coords = [ 109 | *new_upper_left_corner, 110 | *new_upper_right_corner, 111 | *new_point_right_corner, 112 | *new_lower_right_corner, 113 | *new_lower_left_corner, 114 | ] 115 | new_center = _calculate_new_center_of_the_polygon(event_x, event_y, width) 116 | _move_polygon_in_canvas(polygon_id, new_coords, new_center) 117 | 118 | 119 | def _determine_width_of_the_polygon(polygon_id): 120 | polygon_coords = main_window.canvas.coords(polygon_id) 121 | return polygon_coords[2] - polygon_coords[0] 122 | 123 | 124 | def _determine_height_of_the_polygon(polygon_id): 125 | polygon_coords = main_window.canvas.coords(polygon_id) 126 | return polygon_coords[9] - polygon_coords[1] 127 | 128 | 129 | def _calculate_new_upper_left_corner_of_the_polygon(event_x, event_y, width, height) -> list: 130 | return [event_x - 13 * width / 10, event_y - height / 2] 131 | 132 | 133 | def _calculate_new_upper_right_corner_of_the_polygon(event_x, event_y, width, height) -> list: 134 | return [event_x - 3 * width / 10, event_y - height / 2] 135 | 136 | 137 | def _calculate_new_lower_right_corner_of_the_polygon(event_x, event_y, width, height) -> list: 138 | return [event_x - 3 * width / 10, event_y + height / 2] 139 | 140 | 141 | def _calculate_new_lower_left_corner_of_the_polygon(event_x, event_y, width, height) -> list: 142 | return [event_x - 13 * width / 10, event_y + height / 2] 143 | 144 | 145 | def _calculate_new_center_of_the_polygon(event_x, event_y, width) -> list: 146 | return [event_x - 4 * width / 5, event_y] 147 | 148 | 149 | def _move_polygon_in_canvas(polygon_id, new_coords, new_center) -> None: 150 | main_window.canvas.coords(polygon_id, *new_coords) 151 | main_window.canvas.coords("reset_text", *new_center) 152 | -------------------------------------------------------------------------------- /tests/test_output/regression1_fsm.vhd: -------------------------------------------------------------------------------- 1 | -- Filename: regression1_fsm.vhd 2 | -- Created by HDL-FSM-Editor at Wed Aug 13 17:52:13 2025 3 | 4 | architecture fsm of regression1 is 5 | type t_state is (S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, 6 | S11, S12, S13, S14, S15); 7 | signal state : t_state; 8 | begin 9 | p_states: process (res_i, clk_i) 10 | begin 11 | if res_i='1' then 12 | state <= S1; 13 | elsif rising_edge(clk_i) then 14 | -- Global Actions before: 15 | global clocked before 16 | -- State Machine: 17 | case state is 18 | when S1 => 19 | state <= S2; 20 | when S2 => 21 | action1 22 | state <= S3; 23 | when S3 => 24 | if cond2 then 25 | state <= S4; 26 | end if; 27 | when S4 => 28 | if cond3 then 29 | action3 30 | state <= S5; 31 | end if; 32 | when S5 => 33 | state <= S6; 34 | when S6 => 35 | if cond4 then 36 | state <= S7; 37 | else 38 | state <= S8; 39 | end if; 40 | when S7 => 41 | if cond5 then 42 | action5 43 | state <= S10; 44 | else 45 | action51 46 | state <= S9; 47 | end if; 48 | when S8 => 49 | if cond8 then 50 | if cond10 then 51 | action10 52 | if cond11 then 53 | state <= S15; 54 | else 55 | action12 56 | state <= S14; 57 | end if; 58 | else 59 | state <= S13; 60 | end if; 61 | end if; 62 | when S9 => 63 | if cond10 then 64 | action10 65 | if cond11 then 66 | state <= S15; 67 | else 68 | action12 69 | state <= S14; 70 | end if; 71 | else 72 | state <= S13; 73 | end if; 74 | when S10 => 75 | if cond6 then 76 | action6 77 | if cond7 then 78 | action7 79 | state <= S11; 80 | else 81 | state <= S12; 82 | end if; 83 | else 84 | state <= S9; 85 | end if; 86 | when S11 => 87 | if cond12 then 88 | else 89 | state <= S5; 90 | end if; 91 | when S12 => 92 | if cond20 then 93 | if cond21 then 94 | if cond22 then 95 | if cond30 then 96 | action20 97 | action21 98 | action22 99 | action30 100 | state <= S15; 101 | end if; 102 | elsif cond30 then 103 | action20 104 | action21 105 | action22_else 106 | action30 107 | state <= S15; 108 | end if; 109 | elsif cond24 then 110 | if cond30 then 111 | action20 112 | action23 113 | action24 114 | action30 115 | state <= S15; 116 | end if; 117 | elsif cond25 then 118 | if cond30 then 119 | action20 120 | action23 121 | action25 122 | action30 123 | state <= S15; 124 | end if; 125 | else 126 | action20 127 | action23 128 | state <= S15; 129 | end if; 130 | elsif cond10 then 131 | action9 132 | action10 133 | if cond11 then 134 | state <= S15; 135 | else 136 | action12 137 | state <= S14; 138 | end if; 139 | else 140 | action9 141 | state <= S13; 142 | end if; 143 | when S13 => 144 | when S14 => 145 | when S15 => 146 | end case; 147 | -- Global Actions after: 148 | global clocked after 149 | end if; 150 | end process; 151 | p_state_actions: process (state) 152 | begin 153 | -- Default State Actions: 154 | default 155 | -- State Actions: 156 | case state is 157 | when S1=> 158 | stateaction 159 | when S2=> 160 | null; 161 | when S3=> 162 | null; 163 | when S4=> 164 | null; 165 | when S5=> 166 | null; 167 | when S6=> 168 | null; 169 | when S7=> 170 | null; 171 | when S8=> 172 | null; 173 | when S9=> 174 | null; 175 | when S10=> 176 | null; 177 | when S11=> 178 | null; 179 | when S12=> 180 | null; 181 | when S13=> 182 | null; 183 | when S14=> 184 | null; 185 | when S15=> 186 | null; 187 | end case; 188 | end process; 189 | -- Global Actions combinatorial: 190 | global comb 191 | end architecture; 192 | -------------------------------------------------------------------------------- /src/state_action_handling.py: -------------------------------------------------------------------------------- 1 | """ 2 | Handles the state action of a single state. 3 | """ 4 | 5 | import tkinter as tk 6 | from tkinter import ttk 7 | 8 | import canvas_editing 9 | import custom_text 10 | import main_window 11 | import move_handling_canvas_window 12 | import undo_handling 13 | 14 | 15 | class MyText: 16 | """ 17 | Handles the state action of a single state. 18 | """ 19 | 20 | mytext_id = 0 21 | mytext_dict = {} 22 | 23 | def __init__(self, menu_x, menu_y, height, width, padding, increment) -> None: 24 | if increment is True: 25 | MyText.mytext_id += 1 26 | self.text_content = None 27 | self.difference_x = 0 28 | self.difference_y = 0 29 | self.line_id = None 30 | # Create frame: 31 | self.frame_id = ttk.Frame( 32 | main_window.canvas, relief=tk.FLAT, borderwidth=0, padding=padding, style="StateActionsWindow.TFrame" 33 | ) 34 | self.frame_id.bind("", lambda event: self.activate_frame()) 35 | self.frame_id.bind("", lambda event: self.deactivate_frame()) 36 | # Create label object inside frame: 37 | self.label_id = ttk.Label( 38 | self.frame_id, 39 | text="State actions (combinatorial): ", 40 | font=("Arial", int(canvas_editing.label_fontsize)), 41 | style="StateActionsWindow.TLabel", 42 | ) 43 | self.label_id.bind("", lambda event: self.activate_window()) 44 | self.label_id.bind("", lambda event: self.deactivate_window()) 45 | # Create text object inside frame: 46 | self.text_id = custom_text.CustomText( 47 | self.frame_id, 48 | text_type="action", 49 | height=height, 50 | width=width, 51 | undo=True, 52 | maxundo=-1, 53 | font=("Courier", int(canvas_editing.fontsize)), 54 | ) 55 | self.text_id.bind("", lambda event: self.text_id.undo()) 56 | self.text_id.bind("", lambda event: self.text_id.redo()) 57 | self.text_id.bind("", lambda event: self._edit_in_external_editor()) 58 | self.text_id.bind("", lambda event: self.update_text()) 59 | self.text_id.bind("", lambda event: self.update_text()) 60 | self.text_id.bind("<>", lambda event: undo_handling.update_window_title()) 61 | self.text_id.bind("", lambda event: main_window.canvas.unbind_all("")) 62 | self.text_id.bind( 63 | "", lambda event: main_window.canvas.bind_all("", lambda event: canvas_editing.delete()) 64 | ) 65 | 66 | self.label_id.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E)) 67 | self.text_id.grid(column=0, row=1, sticky=(tk.S, tk.W, tk.E)) 68 | 69 | # Create canvas window for frame and text: 70 | self.window_id = main_window.canvas.create_window(menu_x + 100, menu_y, window=self.frame_id, anchor=tk.W) 71 | 72 | self.frame_id.bind( 73 | "", 74 | lambda event: move_handling_canvas_window.MoveHandlingCanvasWindow(event, self.frame_id, self.window_id), 75 | ) 76 | self.label_id.bind( 77 | "", 78 | lambda event: move_handling_canvas_window.MoveHandlingCanvasWindow(event, self.label_id, self.window_id), 79 | ) 80 | 81 | MyText.mytext_dict[self.window_id] = self 82 | 83 | def tag(self) -> None: 84 | main_window.canvas.itemconfigure( 85 | self.window_id, 86 | tag=("state_action" + str(MyText.mytext_id), "connection" + str(MyText.mytext_id) + "_start"), 87 | ) 88 | 89 | def connect_to_state(self, menu_x, menu_y, state_id) -> None: 90 | # Draw a line from the state to the action block which is added to the state: 91 | state_coords = main_window.canvas.coords(state_id) 92 | main_window.canvas.addtag_withtag("connection" + str(MyText.mytext_id) + "_end", state_id) 93 | state_tags = main_window.canvas.gettags(state_id) 94 | self.line_id = main_window.canvas.create_line( 95 | menu_x + 100, 96 | menu_y, 97 | (state_coords[2] + state_coords[0]) / 2, 98 | (state_coords[3] + state_coords[1]) / 2, 99 | dash=(2, 2), 100 | tag=("connection" + str(MyText.mytext_id), "connected_to_" + state_tags[0]), 101 | ) 102 | main_window.canvas.tag_lower(self.line_id, state_id) 103 | 104 | def _edit_in_external_editor(self): 105 | self.text_id.edit_in_external_editor() 106 | self.update_text() 107 | 108 | def update_text(self) -> None: 109 | # Update self.text_content, so that the -check in deactivate_frame() does not signal a design-change and 110 | # that save_in_file_new() already reads the new text, entered into the textbox before Control-s/g. 111 | # To ensure this, save_in_file_new() waits for idle. 112 | self.text_content = self.text_id.get("1.0", tk.END) 113 | 114 | def activate_frame(self) -> None: 115 | self.activate_window() 116 | self.text_content = self.text_id.get("1.0", tk.END) 117 | 118 | def activate_window(self) -> None: 119 | self.frame_id.configure(borderwidth=1, style="StateActionsWindowSelected.TFrame") 120 | self.label_id.configure(style="StateActionsWindowSelected.TLabel") 121 | 122 | def deactivate_frame(self) -> None: 123 | self.deactivate_window() 124 | self.frame_id.focus() # "unfocus" the Text, when the mouse leaves the text. 125 | if self.text_id.get("1.0", tk.END) != self.text_content: 126 | undo_handling.design_has_changed() 127 | 128 | def deactivate_window(self) -> None: 129 | self.frame_id.configure(borderwidth=0, style="StateActionsWindow.TFrame") 130 | self.label_id.configure(style="StateActionsWindow.TLabel") 131 | 132 | def move_to(self, event_x, event_y, first) -> None: 133 | if first: 134 | # Calculate the difference between the "anchor" point and the event: 135 | coords = main_window.canvas.coords(self.window_id) 136 | self.difference_x, self.difference_y = -event_x + coords[0], -event_y + coords[1] 137 | # Keep the distance between event and anchor point constant: 138 | event_x, event_y = event_x + self.difference_x, event_y + self.difference_y 139 | main_window.canvas.coords(self.window_id, event_x, event_y) 140 | # Move the connection line: 141 | window_tags = main_window.canvas.gettags(self.window_id) 142 | for t in window_tags: 143 | if t.startswith("connection"): 144 | line_tag = t[:-6] 145 | line_coords = main_window.canvas.coords(line_tag) 146 | line_coords[0] = event_x 147 | line_coords[1] = event_y 148 | main_window.canvas.coords(line_tag, line_coords) 149 | -------------------------------------------------------------------------------- /src/state_comment.py: -------------------------------------------------------------------------------- 1 | """ 2 | This class handles "state-comments". 3 | """ 4 | 5 | import tkinter as tk 6 | from tkinter import ttk 7 | 8 | import canvas_editing 9 | import custom_text 10 | import main_window 11 | import move_handling_canvas_window 12 | import undo_handling 13 | 14 | 15 | class StateComment: 16 | """ 17 | This class handles "state-comments". 18 | """ 19 | 20 | dictionary = {} 21 | 22 | def __init__( 23 | self, 24 | menu_x, 25 | menu_y, # coordinates for placing the StateComment-Window "near" the clicked menu-entry 26 | height, 27 | width, 28 | padding, 29 | ) -> None: 30 | self.text_content = None 31 | self.difference_x = 0 32 | self.difference_y = 0 33 | self.line_id = None 34 | self.line_coords = [] 35 | # Create frame: 36 | self.frame_id = ttk.Frame( 37 | main_window.canvas, relief=tk.FLAT, borderwidth=0, style="StateActionsWindow.TFrame", padding=padding 38 | ) 39 | self.frame_id.bind("", lambda event: self.activate_frame()) 40 | self.frame_id.bind("", lambda event: self.deactivate_frame()) 41 | # Create label object inside frame: 42 | self.label_id = ttk.Label( 43 | self.frame_id, 44 | text="State-Comment: ", 45 | font=("Arial", int(canvas_editing.label_fontsize)), 46 | style="StateActionsWindow.TLabel", 47 | ) 48 | self.label_id.bind("", lambda event: self.activate_window()) 49 | self.label_id.bind("", lambda event: self.deactivate_window()) 50 | # Create text object inside frame: 51 | self.text_id = custom_text.CustomText( 52 | self.frame_id, 53 | text_type="comment", 54 | height=height, 55 | width=width, 56 | undo=True, 57 | maxundo=-1, 58 | font=("Courier", int(canvas_editing.fontsize)), 59 | foreground="blue", 60 | ) 61 | self.text_id.bind("", lambda event: self.text_id.undo()) 62 | self.text_id.bind("", lambda event: self.text_id.redo()) 63 | self.text_id.bind("", lambda event: self._edit_in_external_editor()) 64 | self.text_id.bind("", lambda event: self.update_text()) 65 | self.text_id.bind("", lambda event: self.update_text()) 66 | self.text_id.bind("<>", lambda event: undo_handling.update_window_title()) 67 | self.text_id.bind("", lambda event: main_window.canvas.unbind_all("")) 68 | self.text_id.bind( 69 | "", lambda event: main_window.canvas.bind_all("", lambda event: canvas_editing.delete()) 70 | ) 71 | 72 | self.label_id.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E)) 73 | self.text_id.grid(column=0, row=1, sticky=(tk.S, tk.W, tk.E)) 74 | 75 | # Create canvas window for frame and text: 76 | self.window_id = main_window.canvas.create_window(menu_x + 100, menu_y, window=self.frame_id, anchor=tk.W) 77 | 78 | self.frame_id.bind( 79 | "", 80 | lambda event: move_handling_canvas_window.MoveHandlingCanvasWindow(event, self.frame_id, self.window_id), 81 | ) 82 | self.label_id.bind( 83 | "", 84 | lambda event: move_handling_canvas_window.MoveHandlingCanvasWindow(event, self.label_id, self.window_id), 85 | ) 86 | StateComment.dictionary[self.window_id] = self # Store the object-reference with the Canvas-id as key. 87 | 88 | def _edit_in_external_editor(self): 89 | self.text_id.edit_in_external_editor() 90 | self.update_text() 91 | 92 | def update_text(self) -> None: 93 | # Update self.text_content, so that the -check in deactivate_frame() does not signal a design-change and 94 | # that save_in_file_new() already reads the new text, entered into the textbox before Control-s/g. 95 | # To ensure this, save_in_file_new() waits for idle. 96 | self.text_content = self.text_id.get("1.0", tk.END) 97 | 98 | def activate_frame(self) -> None: 99 | self.activate_window() 100 | self.text_content = self.text_id.get("1.0", tk.END) 101 | 102 | def activate_window(self) -> None: 103 | self.frame_id.configure(borderwidth=1, style="StateActionsWindowSelected.TFrame") 104 | self.label_id.configure(style="StateActionsWindowSelected.TLabel") 105 | 106 | def deactivate_frame(self) -> None: 107 | self.deactivate_window() 108 | self.frame_id.focus() # "unfocus" the Text, when the mouse leaves the text. 109 | if self.text_id.get("1.0", tk.END) != self.text_content: 110 | undo_handling.design_has_changed() 111 | 112 | def deactivate_window(self) -> None: 113 | self.frame_id.configure(borderwidth=0, style="StateActionsWindow.TFrame") 114 | self.label_id.configure(style="StateActionsWindow.TLabel") 115 | 116 | def move_to(self, event_x, event_y, first) -> None: 117 | if first: 118 | # Calculate the difference between the "anchor" point and the event: 119 | coords = main_window.canvas.coords(self.window_id) 120 | self.difference_x, self.difference_y = -event_x + coords[0], -event_y + coords[1] 121 | # Keep the distance between event and anchor point constant: 122 | event_x, event_y = event_x + self.difference_x, event_y + self.difference_y 123 | main_window.canvas.coords(self.window_id, event_x, event_y) 124 | # Move the connection line: 125 | window_tags = main_window.canvas.gettags(self.window_id) 126 | for t in window_tags: 127 | if t.endswith("_comment"): 128 | line_tag = t + "_line" 129 | self.line_coords = main_window.canvas.coords(line_tag) 130 | self.line_coords[0] = event_x 131 | self.line_coords[1] = event_y 132 | main_window.canvas.coords(line_tag, self.line_coords) 133 | 134 | def add_line(self, menu_x, menu_y, state_identifier) -> None: # Called by state_handling.evaluate_menu(). 135 | # Draw a line from the state to the comment block which is added to the state: 136 | state_coords = main_window.canvas.coords(state_identifier) 137 | self.line_id = main_window.canvas.create_line( 138 | menu_x + 100, 139 | menu_y, 140 | (state_coords[2] + state_coords[0]) / 2, 141 | (state_coords[3] + state_coords[1]) / 2, 142 | dash=(2, 2), 143 | tag=state_identifier + "_comment_line", 144 | ) 145 | main_window.canvas.tag_lower(self.line_id, state_identifier) 146 | 147 | def tag(self, state_identifier) -> None: # Called by state_handling.evaluate_menu(). 148 | main_window.canvas.addtag_withtag(state_identifier + "_comment_line_end", state_identifier) 149 | main_window.canvas.itemconfigure( 150 | self.window_id, tag=(state_identifier + "_comment", state_identifier + "_comment_line_start") 151 | ) 152 | -------------------------------------------------------------------------------- /src/compile_handling.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module implements all methods executes the compile command stored in the Control-tab. 3 | """ 4 | 5 | import os 6 | import re 7 | import shlex 8 | import subprocess 9 | import tkinter as tk 10 | from datetime import datetime 11 | from os.path import exists 12 | from tkinter import messagebox 13 | 14 | import main_window 15 | from constants import GuiTab 16 | 17 | 18 | def compile_hdl() -> None: 19 | main_window.show_tab(GuiTab.COMPILE_MSG) 20 | if main_window.working_directory_value.get() != "" and not main_window.working_directory_value.get().isspace(): 21 | try: 22 | os.chdir(main_window.working_directory_value.get()) 23 | except FileNotFoundError: 24 | messagebox.showerror( 25 | "Error", "The working directory\n" + main_window.working_directory_value.get() + "\ndoes not exist." 26 | ) 27 | return 28 | main_window.log_frame_text.config(state=tk.NORMAL) 29 | main_window.log_frame_text.insert( 30 | tk.END, 31 | "\n++++++++++++++++++++++++++++++++++++++ " 32 | + datetime.today().ctime() 33 | + " +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n", 34 | ) 35 | main_window.log_frame_text.config(state=tk.DISABLED) 36 | start_time = datetime.now() 37 | commands = _get_command_list() 38 | # print("compile_handling: commands =", commands) 39 | for command in commands: 40 | success = _execute(command) 41 | if not success: 42 | break 43 | end_time = datetime.now() 44 | main_window.log_frame_text.config(state=tk.NORMAL) 45 | _insert_line_in_log("Finished user commands from Control-Tab after " + str(end_time - start_time) + ".\n") 46 | main_window.log_frame_text.config(state=tk.DISABLED) 47 | 48 | 49 | def _execute(command) -> bool: 50 | command_array = shlex.split(command) # Does not split quoted sub-strings with blanks. 51 | command_array_new = _replace_variables(command_array) 52 | if command_array_new is None: 53 | return False 54 | for command_part in command_array_new: 55 | _insert_line_in_log(command_part + " ") 56 | _insert_line_in_log("\n") 57 | try: 58 | process = subprocess.Popen( 59 | command_array_new, 60 | text=True, # Decoding is done by Popen. 61 | stdout=subprocess.PIPE, 62 | stderr=subprocess.STDOUT, 63 | ) 64 | for line in process.stdout: # Terminates when process.stdout is closed. 65 | if line != "\n": # VHDL report-statements cause empty lines which mess up the protocol. 66 | _insert_line_in_log(line) 67 | except FileNotFoundError: 68 | command_string = "" 69 | for word in command_array_new: 70 | command_string += word + " " 71 | messagebox.showerror( 72 | "Error in HDL-FSM-Editor", "FileNotFoundError caused by compile command:\n" + command_string 73 | ) 74 | return False 75 | return True 76 | 77 | 78 | def _get_command_list(): 79 | command_string_tmp = main_window.compile_cmd.get() 80 | command_string = command_string_tmp.replace(";", " ; ") 81 | return command_string.split(";") 82 | 83 | 84 | def _replace_variables(command_array) -> list | None: 85 | command_array_new = [] 86 | for entry in command_array: 87 | if entry == "$file": 88 | if main_window.select_file_number_text.get() == 2: 89 | messagebox.showerror( 90 | "Error", 91 | 'The compile command uses $file, but the "2 files mode" is selected,\ 92 | so only $file1 and $file2 are allowed.', 93 | ) 94 | return 95 | language = main_window.language.get() 96 | if language == "VHDL": 97 | extension = ".vhd" 98 | elif language == "Verilog": 99 | extension = ".v" 100 | else: 101 | extension = ".sv" 102 | file_name = main_window.generate_path_value.get() + "/" + main_window.module_name.get() + extension 103 | if not exists(file_name): 104 | messagebox.showerror("Error", "Compile is not possible, HDL file " + file_name + " does not exist.") 105 | return 106 | command_array_new.append(file_name) 107 | elif entry == "$file1": 108 | if main_window.select_file_number_text.get() == 1: 109 | messagebox.showerror( 110 | "Error", 111 | 'The compile command uses $file1, but the "1 files mode" is selected, so only $file is allowed).', 112 | ) 113 | return 114 | file_name1 = main_window.generate_path_value.get() + "/" + main_window.module_name.get() + "_e.vhd" 115 | if not exists(file_name1): 116 | messagebox.showerror("Error", "Compile is not possible, as HDL file" + file_name1 + " does not exist.") 117 | return 118 | command_array_new.append(file_name1) 119 | elif entry == "$file2": 120 | if main_window.select_file_number_text.get() == 1: 121 | messagebox.showerror( 122 | "Error", 123 | 'The compile command uses $file2, but the "1 files mode" is selected, so only $file is allowed).', 124 | ) 125 | return 126 | file_name2 = main_window.generate_path_value.get() + "/" + main_window.module_name.get() + "_fsm.vhd" 127 | if not exists(file_name2): 128 | messagebox.showerror("Error", "Compile is not possible, as HDL file" + file_name2 + " does not exist.") 129 | return 130 | command_array_new.append(file_name2) 131 | elif entry == "$name": 132 | command_array_new.append(main_window.module_name.get()) 133 | else: 134 | command_array_new.append(entry) 135 | return command_array_new 136 | 137 | 138 | def _insert_line_in_log(line) -> None: 139 | if main_window.language.get() == "VHDL": 140 | # search for compiler-message with ":::": 141 | regex_message_find = main_window.regex_message_find_for_vhdl 142 | else: 143 | regex_message_find = main_window.regex_message_find_for_verilog 144 | try: 145 | match_object_of_message = re.match(regex_message_find, line) 146 | except re.error as e: 147 | print("Error in HDL-FSM-Editor by regular expression", repr(e)) 148 | return 149 | 150 | line_low = line.lower() 151 | main_window.log_frame_text.config(state=tk.NORMAL) 152 | if match_object_of_message is not None or " error " in line_low or " warning " in line_low: 153 | # Add line together with color-tag to the text: 154 | if main_window.language.get() == "VHDL" and "report note" in line_low: 155 | main_window.log_frame_text.insert(tk.END, line, ("message_green")) 156 | else: 157 | main_window.log_frame_text.insert(tk.END, line, ("message_red")) 158 | else: 159 | main_window.log_frame_text.insert(tk.END, line) 160 | main_window.log_frame_text.config(state=tk.DISABLED) 161 | main_window.log_frame_text.see(tk.END) 162 | -------------------------------------------------------------------------------- /src/move_handling.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains a method to decide which graphical object must be moved. 3 | """ 4 | 5 | import canvas_editing 6 | import condition_action_handling 7 | import connector_handling 8 | import constants 9 | import global_actions 10 | import global_actions_combinatorial 11 | import main_window 12 | import reset_entry_handling 13 | import state_action_handling 14 | import state_actions_default 15 | import state_comment 16 | import state_handling 17 | import transition_handling 18 | 19 | 20 | def move_do(event, move_list, first, move_to_grid=False) -> None: 21 | # move_to_grid = bool(event.type == "5") 22 | [event_x, event_y] = canvas_editing.translate_window_event_coordinates_in_exact_canvas_coordinates(event) 23 | move_to_coordinates(event_x, event_y, move_list, first, move_to_grid) 24 | 25 | 26 | def move_to_coordinates(event_x, event_y, move_list, first, move_to_grid): 27 | if _state_is_moved_to_near_to_state_or_connector(move_list, event_x, event_y): 28 | return 29 | if _connector_moved_too_close_to_other_object(move_list, event_x, event_y): 30 | return 31 | for entry in move_list: 32 | item_id = entry[0] 33 | item_point_to_move = entry[1] 34 | item_type = main_window.canvas.type(item_id) 35 | if item_type == "oval": 36 | state_handling.move_to(event_x, event_y, item_id, first, move_to_grid) 37 | elif item_type == "polygon": 38 | reset_entry_handling.move_to(event_x, event_y, item_id, first, move_to_grid) 39 | elif item_type == "line": 40 | transition_handling.move_to(event_x, event_y, item_id, item_point_to_move, first, move_list, move_to_grid) 41 | elif item_type == "rectangle": 42 | connector_handling.move_to(event_x, event_y, item_id, first, move_to_grid) 43 | elif item_type == "window": 44 | if item_id in state_action_handling.MyText.mytext_dict: 45 | ref = state_action_handling.MyText.mytext_dict[item_id] 46 | elif item_id in state_comment.StateComment.dictionary: 47 | ref = state_comment.StateComment.dictionary[item_id] 48 | elif item_id in state_actions_default.StateActionsDefault.dictionary: 49 | ref = state_actions_default.StateActionsDefault.dictionary[item_id] 50 | elif item_id in global_actions.GlobalActions.dictionary: 51 | ref = global_actions.GlobalActions.dictionary[item_id] 52 | elif item_id in global_actions_combinatorial.GlobalActionsCombinatorial.dictionary: 53 | ref = global_actions_combinatorial.GlobalActionsCombinatorial.dictionary[item_id] 54 | else: 55 | ref = condition_action_handling.ConditionAction.dictionary[item_id] 56 | ref.move_to(event_x, event_y, first) 57 | else: 58 | print("move: Fatal, unknown canvas type", "|" + item_type + "|") 59 | 60 | 61 | def _state_is_moved_to_near_to_state_or_connector(move_list, event_x, event_y) -> bool: 62 | for entry in move_list: 63 | moved_item_id = entry[0] 64 | if main_window.canvas.type(moved_item_id) == "oval": 65 | # Keep the distance between event and anchor point constant: 66 | event_x_mod, event_y_mod = event_x + state_handling.difference_x, event_y + state_handling.difference_y 67 | event_x_mod = canvas_editing.state_radius * round(event_x_mod / canvas_editing.state_radius) 68 | event_y_mod = canvas_editing.state_radius * round(event_y_mod / canvas_editing.state_radius) 69 | state_coords = main_window.canvas.coords(moved_item_id) 70 | state_radius = (state_coords[2] - state_coords[0]) // 2 71 | moved_state_coords = ( 72 | event_x_mod - state_radius, 73 | event_y_mod - state_radius, 74 | event_x_mod + state_radius, 75 | event_y_mod + state_radius, 76 | ) 77 | overlapping_list = main_window.canvas.find_overlapping( 78 | moved_state_coords[0] - canvas_editing.state_radius / 2, 79 | moved_state_coords[1] - canvas_editing.state_radius / 2, 80 | moved_state_coords[2] + canvas_editing.state_radius / 2, 81 | moved_state_coords[3] + canvas_editing.state_radius / 2, 82 | ) 83 | for overlapping_item in overlapping_list: 84 | overlapping_with_connector = False 85 | tags = main_window.canvas.gettags(overlapping_item) 86 | for tag in tags: 87 | if tag.startswith("connector"): 88 | overlapping_with_connector = True 89 | if overlapping_item != moved_item_id and ( 90 | main_window.canvas.type(overlapping_item) == "oval" or overlapping_with_connector 91 | ): 92 | return True 93 | return False 94 | 95 | 96 | def _connector_moved_too_close_to_other_object(move_list, event_x, event_y) -> bool: 97 | for entry in move_list: 98 | moved_item_id = entry[0] 99 | if ( 100 | main_window.canvas.type(moved_item_id) == "rectangle" 101 | and main_window.canvas.itemcget(moved_item_id, "fill") == constants.CONNECTOR_COLOR 102 | ): 103 | # Keep the distance between event and anchor point constant: 104 | event_x_mod, event_y_mod = ( 105 | event_x + connector_handling.difference_x, 106 | event_y + connector_handling.difference_y, 107 | ) 108 | event_x_mod = canvas_editing.state_radius * round( 109 | event_x_mod / canvas_editing.state_radius 110 | ) # move event_x to grid. 111 | event_y_mod = canvas_editing.state_radius * round( 112 | event_y_mod / canvas_editing.state_radius 113 | ) # move event_y to grid. 114 | connector_coords = main_window.canvas.coords(moved_item_id) 115 | edge_length = connector_coords[2] - connector_coords[0] 116 | new_upper_left_corner = [event_x_mod - edge_length / 2, event_y_mod - edge_length / 2] 117 | new_lower_right_corner = [event_x_mod + edge_length / 2, event_y_mod + edge_length / 2] 118 | moved_connector_coords = [*new_upper_left_corner, *new_lower_right_corner] 119 | overlapping_list = main_window.canvas.find_overlapping( 120 | moved_connector_coords[0] - canvas_editing.state_radius / 2, 121 | moved_connector_coords[1] - canvas_editing.state_radius / 2, 122 | moved_connector_coords[2] + canvas_editing.state_radius / 2, 123 | moved_connector_coords[3] + canvas_editing.state_radius / 2, 124 | ) 125 | for overlapping_item in overlapping_list: 126 | if overlapping_item != moved_item_id and ( 127 | main_window.canvas.type(overlapping_item) == "oval" 128 | or ( 129 | main_window.canvas.type(overlapping_item) == "rectangle" 130 | and main_window.canvas.itemcget(overlapping_item, "fill") == constants.CONNECTOR_COLOR 131 | ) 132 | ): 133 | return True 134 | return False 135 | -------------------------------------------------------------------------------- /tests/test_input/fifo_test_error.hfe: -------------------------------------------------------------------------------- 1 | { 2 | "modulename": "fifo_test", 3 | "language": "VHDL", 4 | "generate_path": ".", 5 | "working_directory": ".", 6 | "number_of_files": 2, 7 | "reset_signal_name": "res_i", 8 | "clock_signal_name": "clk_i", 9 | "compile_cmd": "ghdl -a $file1 $file2; ghdl -e $name; ghdl -r $name", 10 | "edit_cmd": "C:/Program Files/Notepad++/notepad++.exe -nosession -multiInst", 11 | "diagram_background_color": "#fcfdc6", 12 | "include_timestamp_in_output": false, 13 | "state_number": 2, 14 | "transition_number": 3, 15 | "reset_entry_number": 1, 16 | "connector_number": 0, 17 | "conditionaction_id": 2, 18 | "mytext_id": 0, 19 | "global_actions_number": 0, 20 | "state_actions_default_number": 0, 21 | "global_actions_combinatorial_number": 0, 22 | "state_radius": 20.0, 23 | "reset_entry_size": 40, 24 | "priority_distance": 14, 25 | "fontsize": 10, 26 | "label_fontsize": 8, 27 | "visible_center": "109.5 306.5 ", 28 | "interface_package": "", 29 | "interface_generics": "", 30 | "interface_ports": "", 31 | "internals_package": "", 32 | "internals_architecture": "", 33 | "internals_process": "", 34 | "internals_process_combinatorial": "", 35 | "sash_positions": { 36 | "interface_tab": { 37 | "0": 135, 38 | "1": 275 39 | }, 40 | "internals_tab": { 41 | "0": 100, 42 | "1": 205, 43 | "2": 310 44 | } 45 | }, 46 | "state": [ 47 | [ 48 | [ 49 | 0.0, 50 | 100.0, 51 | 40.0, 52 | 140.0 53 | ], 54 | [ 55 | "state1", 56 | "transition0_end", 57 | "transition1_start", 58 | "transition2_start" 59 | ], 60 | "cyan" 61 | ], 62 | [ 63 | [ 64 | 0.0, 65 | 340.0, 66 | 40.0, 67 | 380.0 68 | ], 69 | [ 70 | "state2", 71 | "transition1_end", 72 | "transition2_end" 73 | ], 74 | "cyan" 75 | ] 76 | ], 77 | "text": [ 78 | [ 79 | [ 80 | -172.0, 81 | 120.0 82 | ], 83 | [ 84 | "reset_text" 85 | ], 86 | "Reset" 87 | ], 88 | [ 89 | [ 90 | 20.0, 91 | 120.0 92 | ], 93 | [ 94 | "state1_name" 95 | ], 96 | "S1" 97 | ], 98 | [ 99 | [ 100 | 20.0, 101 | 360.0 102 | ], 103 | [ 104 | "state2_name" 105 | ], 106 | "S2" 107 | ], 108 | [ 109 | [ 110 | -126.0, 111 | 120.0 112 | ], 113 | [ 114 | "transition0priority" 115 | ], 116 | "1" 117 | ], 118 | [ 119 | [ 120 | 20.0, 121 | 154.0 122 | ], 123 | [ 124 | "transition1priority" 125 | ], 126 | "1" 127 | ], 128 | [ 129 | [ 130 | 50.205717596792546, 131 | 135.6081589068928 132 | ], 133 | [ 134 | "transition2priority" 135 | ], 136 | "2" 137 | ] 138 | ], 139 | "line": [ 140 | [ 141 | [ 142 | -126.0, 143 | 120.0, 144 | -140.0, 145 | 120.0 146 | ], 147 | [ 148 | "ca_connection2", 149 | "connected_to_transition0" 150 | ] 151 | ], 152 | [ 153 | [ 154 | -140.0, 155 | 120.0, 156 | 0.0, 157 | 120.0 158 | ], 159 | [ 160 | "transition0", 161 | "coming_from_reset_entry", 162 | "going_to_state1", 163 | "ca_connection2_end" 164 | ] 165 | ], 166 | [ 167 | [ 168 | 20.0, 169 | 140.0, 170 | 20.0, 171 | 340.0 172 | ], 173 | [ 174 | "transition1", 175 | "coming_from_state1", 176 | "going_to_state2" 177 | ] 178 | ], 179 | [ 180 | [ 181 | 85.0, 182 | 156.0, 183 | 37.76806917458385, 184 | 129.18126994523107 185 | ], 186 | [ 187 | "ca_connection1", 188 | "connected_to_transition2" 189 | ] 190 | ], 191 | [ 192 | [ 193 | 37.76806917458385, 194 | 129.18126994523107, 195 | 289.0, 196 | 259.0, 197 | 38.72372152196466, 198 | 352.9699038151731 199 | ], 200 | [ 201 | "transition2", 202 | "coming_from_state1", 203 | "going_to_state2", 204 | "ca_connection1_end" 205 | ] 206 | ] 207 | ], 208 | "polygon": [ 209 | [ 210 | [ 211 | -192.0, 212 | 108.0, 213 | -152.0, 214 | 108.0, 215 | -140.0, 216 | 120.0, 217 | -152.0, 218 | 132.0, 219 | -192.0, 220 | 132.0 221 | ], 222 | [ 223 | "reset_entry", 224 | "transition0_start" 225 | ] 226 | ] 227 | ], 228 | "rectangle": [ 229 | [ 230 | [ 231 | -130.0, 232 | 112.0, 233 | -121.0, 234 | 129.0 235 | ], 236 | [ 237 | "transition0rectangle" 238 | ] 239 | ], 240 | [ 241 | [ 242 | 16.0, 243 | 146.0, 244 | 25.0, 245 | 163.0 246 | ], 247 | [ 248 | "transition1rectangle" 249 | ] 250 | ], 251 | [ 252 | [ 253 | 46.0, 254 | 128.0, 255 | 55.0, 256 | 145.0 257 | ], 258 | [ 259 | "transition2rectangle" 260 | ] 261 | ] 262 | ], 263 | "window_state_action_block": [], 264 | "window_state_comment": [], 265 | "window_condition_action_block": [ 266 | [ 267 | [ 268 | 85.0, 269 | 156.0 270 | ], 271 | "ddd", 272 | "", 273 | [ 274 | "condition_action1", 275 | "ca_connection1_anchor" 276 | ] 277 | ], 278 | [ 279 | [ 280 | -126.0, 281 | 120.0 282 | ], 283 | "aaa", 284 | "", 285 | [ 286 | "condition_action2", 287 | "ca_connection2_anchor", 288 | "connected_to_reset_transition" 289 | ] 290 | ] 291 | ], 292 | "window_global_actions": [], 293 | "window_global_actions_combinatorial": [], 294 | "window_state_actions_default": [], 295 | "regex_message_find": "(.*?):([0-9]+):[0-9]+:.*", 296 | "regex_file_name_quote": "\\1", 297 | "regex_file_line_number_quote": "\\2" 298 | } -------------------------------------------------------------------------------- /rsc/hfe_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 204 | -------------------------------------------------------------------------------- /src/global_actions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Handles the global actions window in the diagram. 3 | """ 4 | 5 | import tkinter as tk 6 | from tkinter import ttk 7 | 8 | import canvas_editing 9 | import canvas_modify_bindings 10 | import custom_text 11 | import main_window 12 | import move_handling_canvas_window 13 | import undo_handling 14 | 15 | 16 | class GlobalActions: 17 | """ 18 | Handles the global actions clocked window in the diagram. 19 | """ 20 | 21 | global_actions_number = 1 22 | dictionary = {} 23 | 24 | def __init__(self, menu_x, menu_y, height, width, padding) -> None: 25 | self.text_before_content = None 26 | self.text_after_content = None 27 | self.difference_x = 0 28 | self.difference_y = 0 29 | self.frame_id = ttk.Frame( 30 | main_window.canvas, relief=tk.FLAT, borderwidth=0, padding=padding, style="GlobalActionsWindow.TFrame" 31 | ) 32 | self.frame_id.bind("", lambda event: self.activate_frame()) 33 | self.frame_id.bind("", lambda event: self.deactivate_frame()) 34 | # Create label object inside frame: 35 | self.label_before = ttk.Label( 36 | self.frame_id, 37 | text="Global actions clocked (executed before running the state machine):", 38 | font=("Arial", int(canvas_editing.label_fontsize)), 39 | style="GlobalActionsWindow.TLabel", 40 | ) 41 | self.label_before.bind("", lambda event: self.activate_window()) 42 | self.label_before.bind("", lambda event: self.deactivate_window()) 43 | self.label_after = ttk.Label( 44 | self.frame_id, 45 | text="Global actions clocked (executed after running the state machine):", 46 | font=("Arial", int(canvas_editing.label_fontsize)), 47 | style="GlobalActionsWindow.TLabel", 48 | ) 49 | self.label_after.bind("", lambda event: self.activate_window()) 50 | self.label_after.bind("", lambda event: self.deactivate_window()) 51 | self.text_before_id = custom_text.CustomText( 52 | self.frame_id, 53 | text_type="action", 54 | height=height, 55 | width=width, 56 | undo=True, 57 | maxundo=-1, 58 | font=("Courier", int(canvas_editing.fontsize)), 59 | ) 60 | self.text_after_id = custom_text.CustomText( 61 | self.frame_id, 62 | text_type="action", 63 | height=height, 64 | width=width, 65 | undo=True, 66 | maxundo=-1, 67 | font=("Courier", int(canvas_editing.fontsize)), 68 | ) 69 | self.text_before_id.bind("", lambda event: self.text_before_id.undo()) 70 | self.text_before_id.bind("", lambda event: self.text_before_id.redo()) 71 | self.text_before_id.bind("", lambda event: self._edit_before_in_external_editor()) 72 | self.text_after_id.bind("", lambda event: self.text_after_id.undo()) 73 | self.text_after_id.bind("", lambda event: self.text_after_id.redo()) 74 | self.text_after_id.bind("", lambda event: self._edit_after_in_external_editor()) 75 | self.text_before_id.bind("", lambda event: self.update_before()) 76 | self.text_before_id.bind("", lambda event: self.update_before()) 77 | self.text_after_id.bind("", lambda event: self.update_after()) 78 | self.text_after_id.bind("", lambda event: self.update_after()) 79 | self.text_before_id.bind("<>", lambda event: undo_handling.update_window_title()) 80 | self.text_after_id.bind("<>", lambda event: undo_handling.update_window_title()) 81 | self.text_before_id.bind("", lambda event: main_window.canvas.unbind_all("")) 82 | self.text_before_id.bind( 83 | "", lambda event: main_window.canvas.bind_all("", lambda event: canvas_editing.delete()) 84 | ) 85 | self.text_after_id.bind("", lambda event: main_window.canvas.unbind_all("")) 86 | self.text_after_id.bind( 87 | "", lambda event: main_window.canvas.bind_all("", lambda event: canvas_editing.delete()) 88 | ) 89 | 90 | self.label_before.grid(row=0, column=0, sticky=(tk.N, tk.W, tk.E)) 91 | self.text_before_id.grid(row=1, column=0, sticky=(tk.E, tk.W)) 92 | self.label_after.grid(row=2, column=0, sticky=(tk.E, tk.W)) 93 | self.text_after_id.grid(row=3, column=0, sticky=(tk.E, tk.W, tk.S)) 94 | 95 | # Create canvas window for frame and text: 96 | self.window_id = main_window.canvas.create_window(menu_x, menu_y, window=self.frame_id, anchor=tk.W) 97 | 98 | self.frame_id.bind( 99 | "", 100 | lambda event: move_handling_canvas_window.MoveHandlingCanvasWindow(event, self.frame_id, self.window_id), 101 | ) 102 | self.label_before.bind( 103 | "", 104 | lambda event: move_handling_canvas_window.MoveHandlingCanvasWindow( 105 | event, self.label_before, self.window_id 106 | ), 107 | ) 108 | self.label_after.bind( 109 | "", 110 | lambda event: move_handling_canvas_window.MoveHandlingCanvasWindow(event, self.label_after, self.window_id), 111 | ) 112 | 113 | GlobalActions.dictionary[self.window_id] = self 114 | canvas_modify_bindings.switch_to_move_mode() 115 | 116 | def _edit_before_in_external_editor(self): 117 | self.text_before_id.edit_in_external_editor() 118 | self.update_before() 119 | 120 | def _edit_after_in_external_editor(self): 121 | self.text_after_id.edit_in_external_editor() 122 | self.update_after() 123 | 124 | def update_before(self): 125 | # Update self.text_before_content, so that the -check in deactivate_frame() does not signal a design- 126 | # change and that save_in_file_new() already reads the new text, entered into the textbox before Control-s/g. 127 | # To ensure this, save_in_file_new() waits for idle. 128 | self.text_before_content = self.text_before_id.get("1.0", tk.END) 129 | 130 | def update_after(self): 131 | # Update self.text_after_content, so that the -check in deactivate_frame() does not signal a design- 132 | # change and that save_in_file_new() already reads the new text, entered into the textbox before Control-s/g. 133 | # To ensure this, save_in_file_new() waits for idle. 134 | self.text_after_content = self.text_after_id.get("1.0", tk.END) 135 | 136 | def tag(self) -> None: 137 | main_window.canvas.itemconfigure( 138 | self.window_id, tag="global_actions" + str(GlobalActions.global_actions_number) 139 | ) 140 | 141 | def activate_frame(self) -> None: 142 | self.activate_window() 143 | self.text_before_content = self.text_before_id.get("1.0", tk.END) 144 | self.text_after_content = self.text_after_id.get("1.0", tk.END) 145 | 146 | def activate_window(self) -> None: 147 | self.frame_id.configure(borderwidth=1, style="GlobalActionsWindowSelected.TFrame") 148 | self.label_before.configure(style="GlobalActionsWindowSelected.TLabel") 149 | self.label_after.configure(style="GlobalActionsWindowSelected.TLabel") 150 | 151 | def deactivate_frame(self) -> None: 152 | self.deactivate_window() 153 | self.frame_id.focus() # "unfocus" the Text, when the mouse leaves the text. 154 | if self.text_before_id.get("1.0", tk.END) != self.text_before_content: 155 | undo_handling.design_has_changed() 156 | if self.text_after_id.get("1.0", tk.END) != self.text_after_content: 157 | undo_handling.design_has_changed() 158 | 159 | def deactivate_window(self) -> None: 160 | self.frame_id.configure(borderwidth=0, style="GlobalActionsWindow.TFrame") 161 | self.label_before.configure(style="GlobalActionsWindow.TLabel") 162 | self.label_after.configure(style="GlobalActionsWindow.TLabel") 163 | 164 | def move_to(self, event_x, event_y, first) -> None: 165 | if first: 166 | # Calculate the difference between the "anchor" point and the event: 167 | coords = main_window.canvas.coords(self.window_id) 168 | self.difference_x, self.difference_y = -event_x + coords[0], -event_y + coords[1] 169 | # Keep the distance between event and anchor point constant: 170 | event_x, event_y = event_x + self.difference_x, event_y + self.difference_y 171 | main_window.canvas.coords(self.window_id, event_x, event_y) 172 | -------------------------------------------------------------------------------- /docs/release_script_README.md: -------------------------------------------------------------------------------- 1 | # Release Script README 2 | 3 | ## Overview 4 | The `release_script.py` automates the complete release process for HDL-FSM-Editor, ensuring consistency and proper versioning. It supports both **release builds** and **dev builds**. 5 | 6 | ## Prerequisites 7 | - Python 3.9+ 8 | - Git (with proper remote configured) 9 | - PyInstaller (already in dev dependencies) 10 | - Working directory must be the project root 11 | 12 | ## Usage 13 | 14 | ### Release Build (Full Release) 15 | Creates a complete release with git tagging. Requires clean git state. 16 | ```bash 17 | python release_script.py --release 18 | # or short form: 19 | python release_script.py -r 20 | ``` 21 | 22 | ### Dev Build (Development Build) 23 | Creates a development build without git tagging. Allows dirty git state. 24 | ```bash 25 | python release_script.py --dev 26 | # or short form: 27 | python release_script.py -d 28 | ``` 29 | 30 | ### Dev Build with Custom Version 31 | Override the version from CHANGELOG.md for dev builds: 32 | ```bash 33 | python release_script.py --dev --version 4.12-dev 34 | ``` 35 | 36 | ### Cleanup Build Artifacts 37 | Remove PyInstaller build artifacts (dist/, build/, *.spec files): 38 | ```bash 39 | python release_script.py --cleanup 40 | ``` 41 | 42 | ## Build Types Comparison 43 | 44 | | Feature | Release Build | Dev Build | 45 | |---------|---------------|-----------| 46 | | Git state | Must be clean | Can be dirty | 47 | | Branch check | Warns if not main/master | No branch restrictions | 48 | | Version consistency | Strict enforcement | Warning only | 49 | | Date validation | Required | Optional (warning only) | 50 | | Git tagging | Creates tag | No tagging | 51 | | File naming | `HDL-FSM-Editor-4.12` | `HDL-FSM-Editor-4.12-dev-20250115-143022` | 52 | | Archive naming | `HDL-FSM-Editor-4.12.zip` | `HDL-FSM-Editor-4.12-dev-20250115-143022.zip` | 53 | 54 | ## What the Script Does 55 | 56 | ### For Release Builds: 57 | 1. **Checks git status** - Ensures clean repository and main/master branch 58 | 2. **Parses CHANGELOG.md** - Finds latest version and validates date 59 | 3. **Verifies version consistency** - Ensures `src/main_window.py` matches CHANGELOG.md 60 | 4. **Checks git tag status** - Prompts if tag already exists 61 | 5. **Builds executable** - Uses PyInstaller to create standalone executable 62 | 6. **Creates release directory** - Organizes files in `releases/HDL-FSM-Editor-{version}/` 63 | 7. **Creates ZIP archive** - Packages everything for distribution 64 | 8. **Creates git tag** - Tags the release in git 65 | 66 | ### For Dev Builds: 67 | 1. **Checks git status** - Only verifies git repository exists 68 | 2. **Parses CHANGELOG.md** - Finds latest version (or uses override), date optional 69 | 3. **Verifies version consistency** - Warns but continues if mismatch 70 | 4. **Skips git tag check** - No tagging for dev builds 71 | 5. **Builds executable** - Uses PyInstaller to create standalone executable 72 | 6. **Creates release directory** - Includes timestamp in directory name 73 | 7. **Creates ZIP archive** - Includes timestamp in archive name 74 | 8. **Skips git tag creation** - No tagging for dev builds 75 | 76 | ### For Cleanup: 77 | 1. **Removes dist/ directory** - PyInstaller output directory 78 | 2. **Removes build/ directory** - PyInstaller build directory 79 | 3. **Removes *.spec files** - PyInstaller specification files 80 | 81 | ## Example Output 82 | 83 | ### Release Build: 84 | ``` 85 | 🚀 Starting HDL-FSM-Editor Release Process 86 | ================================================== 87 | 88 | 🔍 Step 1: Checking git status 89 | ✅ Git repository is clean and ready for release 90 | 91 | 📋 Step 2: Parsing CHANGELOG.md 92 | ✅ Found version 4.12 with date 15.01.2025 93 | 94 | 🔍 Step 3: Verifying version consistency 95 | ✅ Version consistency verified: 4.12 96 | 97 | 🏷️ Step 4: Checking git tag status 98 | ✅ Git tag status verified for v4.12 99 | 100 | 🔨 Step 5: Building executable 101 | 🔨 Building executable with PyInstaller... 102 | ✅ Executable created: dist/HDL-FSM-Editor.exe 103 | 104 | 📁 Step 6: Creating release directory 105 | ✅ Release directory created: releases/HDL-FSM-Editor-4.12 106 | 107 | 📦 Step 7: Creating release archive 108 | ✅ Archive created: releases/HDL-FSM-Editor-4.12.zip 109 | 110 | 🏷️ Step 8: Creating git tag 111 | ✅ Git tag created: v4.12 112 | 113 | ================================================== 114 | 🎉 Release completed successfully! 115 | 📦 Release files: 116 | Directory: releases/HDL-FSM-Editor-4.12 117 | Archive: releases/HDL-FSM-Editor-4.12.zip 118 | 🏷️ Git tag: v4.12 119 | 120 | 💡 Next steps: 121 | 1. Push the tag: git push origin v4.12 122 | 2. Upload the archive to your release platform 123 | 3. Update the website with the new version 124 | ``` 125 | 126 | ### Dev Build: 127 | ``` 128 | 🚀 Starting HDL-FSM-Editor Dev Build Process 129 | ================================================== 130 | 131 | 🔍 Step 1: Checking git status 132 | ✅ Git repository found (dev build mode) 133 | 134 | 📋 Step 2: Parsing CHANGELOG.md 135 | ✅ Found version 4.12 with date 15.01.2025 136 | 137 | 🔍 Step 3: Verifying version consistency 138 | ⚠️ Warning: Version mismatch (dev build) 139 | CHANGELOG.md: 4.12 140 | main_window.py: 4.11 141 | Continuing with dev build... 142 | 143 | 🏷️ Step 4: Checking git tag status 144 | ⏭️ Skipping git tag check (dev build) 145 | 146 | 🔨 Step 5: Building executable 147 | 🔨 Building executable with PyInstaller... 148 | ✅ Executable created: dist/HDL-FSM-Editor.exe 149 | 150 | 📁 Step 6: Creating release directory 151 | ✅ Release directory created: releases/HDL-FSM-Editor-4.12-dev-20250115-143022 152 | 153 | 📦 Step 7: Creating release archive 154 | ✅ Archive created: releases/HDL-FSM-Editor-4.12-dev-20250115-143022.zip 155 | 156 | 🏷️ Step 8: Creating git tag 157 | ⏭️ Skipping git tag creation (dev build) 158 | 159 | ================================================== 160 | 🎉 Dev Build completed successfully! 161 | 📦 Release files: 162 | Directory: releases/HDL-FSM-Editor-4.12-dev-20250115-143022 163 | Archive: releases/HDL-FSM-Editor-4.12-dev-20250115-143022.zip 164 | 165 | 💡 Next steps: 166 | 1. Test the dev build 167 | 2. Upload the archive for testing if needed 168 | ``` 169 | 170 | ## Error Handling 171 | 172 | ### Common Errors and Solutions 173 | 174 | #### Git Repository Not Clean (Release Build) 175 | ``` 176 | ❌ Error: Git repository is not clean 177 | Please commit or stash all changes before creating a release 178 | Uncommitted changes: 179 | M src/main_window.py 180 | ``` 181 | **Solution**: Commit or stash changes before running release build 182 | 183 | #### Wrong Branch (Release Build) 184 | ``` 185 | ⚠️ Warning: Not on main/master branch (currently on feature-branch) 186 | Continue anyway? (type 'yes' to confirm): 187 | ``` 188 | **Solution**: Switch to main/master branch or type 'yes' to continue 189 | 190 | #### Version Mismatch 191 | ``` 192 | ❌ Error: Version mismatch! 193 | CHANGELOG.md: 4.12 194 | main_window.py: 4.11 195 | ``` 196 | **Solution**: Update `_VERSION` in `src/main_window.py` to match CHANGELOG.md 197 | 198 | #### Missing Date (Release Build) 199 | ``` 200 | ❌ Error: Version 4.12 has no date assigned in CHANGELOG.md 201 | ``` 202 | **Solution**: Update the date in CHANGELOG.md from `xx.yy.2025` to actual date 203 | 204 | #### Missing Date (Dev Build) 205 | ``` 206 | ⚠️ Warning: Version 4.12 has no date assigned (dev build) 207 | Continuing with dev build... 208 | ``` 209 | **Solution**: This is just a warning for dev builds and will continue normally 210 | 211 | #### Tag Already Exists 212 | ``` 213 | ⚠️ Tag v4.12 already exists 214 | Overwrite? (type 'yes' to confirm): 215 | ``` 216 | **Solution**: Type `yes` to overwrite, or any other input to cancel 217 | 218 | #### PyInstaller Build Failure 219 | ``` 220 | ❌ Error building executable: Command '['pyinstaller', ...]' returned non-zero exit status 1 221 | ``` 222 | **Solution**: 223 | - Check PyInstaller is installed: `pip install pyinstaller` 224 | - Ensure all dependencies are available 225 | - Check for syntax errors in the code 226 | 227 | ## File Structure After Build 228 | 229 | ### Release Build: 230 | ``` 231 | releases/ 232 | ├── HDL-FSM-Editor-4.12/ 233 | │ ├── HDL-FSM-Editor.exe 234 | │ └── CHANGELOG.md 235 | └── HDL-FSM-Editor-4.12.zip 236 | ``` 237 | 238 | ### Dev Build: 239 | ``` 240 | releases/ 241 | ├── HDL-FSM-Editor-4.12-dev-20250115-143022/ 242 | │ ├── HDL-FSM-Editor.exe 243 | │ └── CHANGELOG.md 244 | └── HDL-FSM-Editor-4.12-dev-20250115-143022.zip 245 | ``` 246 | 247 | ## Manual Steps After Script 248 | 249 | ### For Release Builds: 250 | 1. **Push the git tag**: 251 | ```bash 252 | git push origin v4.12 253 | ``` 254 | 255 | 2. **Upload to release platform** (GitHub, GitLab, etc.) 256 | 257 | 3. **Update website** with new version information 258 | 259 | ### For Dev Builds: 260 | 1. **Test the dev build** thoroughly 261 | 262 | 2. **Upload for testing** if needed 263 | 264 | 3. **No git operations** required 265 | 266 | ## Troubleshooting 267 | 268 | ### Git Issues 269 | - Ensure you're in a git repository 270 | - Check remote is configured: `git remote -v` 271 | - Verify you have push permissions 272 | 273 | ### PyInstaller Issues 274 | - Install dev dependencies: `pip install -e ".[dev]"` 275 | - Check Python environment is correct 276 | - Verify all imports are available 277 | 278 | ### File Permission Issues 279 | - Ensure write permissions in project directory 280 | - Check antivirus isn't blocking file operations 281 | 282 | ## Safety Features 283 | - **Version validation** - Prevents releases with mismatched versions 284 | - **Date validation** - Ensures changelog has proper date 285 | - **Tag confirmation** - Prevents accidental tag overwrites 286 | - **Build verification** - Confirms executable was created successfully 287 | - **Archive validation** - Ensures ZIP file is not empty 288 | - **Git state validation** - Ensures clean state for releases 289 | - **Branch validation** - Warns about wrong branch for releases 290 | 291 | ## Script Dependencies 292 | - Standard Python libraries only (no additional packages) 293 | - Uses existing PyInstaller from dev dependencies 294 | - Requires git to be available in PATH -------------------------------------------------------------------------------- /src/codegen/hdl_generation_architecture.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module provides a method for creating the architecture part of a VHDL file. 3 | """ 4 | 5 | import codegen.hdl_generation_architecture_state_actions as hdl_generation_architecture_state_actions 6 | import codegen.hdl_generation_architecture_state_sequence as hdl_generation_architecture_state_sequence 7 | import codegen.hdl_generation_library as hdl_generation_library 8 | import main_window 9 | from link_dictionary import link_dict 10 | 11 | 12 | def create_architecture(file_name, file_line_number, state_tag_list_sorted) -> None: 13 | architecture = "" 14 | 15 | package_statements = hdl_generation_library.get_text_from_text_widget(main_window.internals_package_text) 16 | architecture += package_statements 17 | number_of_new_lines = package_statements.count("\n") 18 | link_dict().add( 19 | file_name, 20 | file_line_number, 21 | "custom_text_in_internals_tab", 22 | number_of_new_lines, 23 | main_window.internals_package_text, 24 | ) 25 | file_line_number += number_of_new_lines 26 | 27 | architecture += "\n" 28 | architecture += "architecture fsm of " + main_window.module_name.get() + " is\n" 29 | architecture += hdl_generation_library.indent_text_by_the_given_number_of_tabs( 30 | 1, _create_type_definition_for_the_state_signal(state_tag_list_sorted) 31 | ) 32 | architecture += " signal state : t_state;\n" 33 | file_line_number += 4 34 | 35 | signal_declarations = hdl_generation_library.get_text_from_text_widget(main_window.internals_architecture_text) 36 | architecture += hdl_generation_library.indent_text_by_the_given_number_of_tabs(1, signal_declarations) 37 | number_of_new_lines = signal_declarations.count("\n") 38 | link_dict().add( 39 | file_name, 40 | file_line_number, 41 | "custom_text_in_internals_tab", 42 | number_of_new_lines, 43 | main_window.internals_architecture_text, 44 | ) 45 | file_line_number += number_of_new_lines 46 | 47 | architecture += "begin\n" 48 | file_line_number += 1 49 | architecture += ( 50 | " p_states: process (" 51 | + main_window.reset_signal_name.get() 52 | + ", " 53 | + main_window.clock_signal_name.get() 54 | + ")\n" 55 | ) 56 | link_dict().add(file_name, file_line_number, "Control-Tab", 1, "reset_and_clock_signal_name") 57 | file_line_number += 1 58 | 59 | variable_declarations = hdl_generation_library.get_text_from_text_widget(main_window.internals_process_clocked_text) 60 | architecture += hdl_generation_library.indent_text_by_the_given_number_of_tabs(2, variable_declarations) 61 | number_of_new_lines = variable_declarations.count("\n") 62 | link_dict().add( 63 | file_name, 64 | file_line_number, 65 | "custom_text_in_internals_tab", 66 | number_of_new_lines, 67 | main_window.internals_process_clocked_text, 68 | ) 69 | file_line_number += number_of_new_lines 70 | 71 | architecture += " begin\n" 72 | file_line_number += 1 73 | 74 | [reset_condition, reset_action, reference_to_reset_condition_custom_text, reference_to_reset_action_custom_text] = ( 75 | hdl_generation_library.create_reset_condition_and_reset_action() 76 | ) 77 | if reset_condition is None: 78 | return # No further actions make sense, as always a reset condition must exist. 79 | if reset_condition.count("\n") == 0: 80 | architecture += " if " + reset_condition + " then\n" 81 | else: 82 | reset_condition_list = reset_condition.split("\n") 83 | for index, line in enumerate(reset_condition_list): 84 | if index == 0: 85 | architecture += " if " + line + "\n" 86 | else: 87 | architecture += " " + line + "\n" 88 | architecture += " then\n" 89 | number_of_new_lines = reset_condition.count("\n") + 1 # No return after the last line of the condition 90 | link_dict().add( 91 | file_name, 92 | file_line_number, 93 | "custom_text_in_diagram_tab", 94 | number_of_new_lines, 95 | reference_to_reset_condition_custom_text, 96 | ) 97 | file_line_number += number_of_new_lines 98 | 99 | architecture += hdl_generation_library.indent_text_by_the_given_number_of_tabs(3, reset_action) 100 | # reset_action starts always with "state <=", which is not a line entered by the user, 101 | # and therefore cannot be linked: 102 | file_line_number += 1 103 | number_of_new_lines = reset_action.count("\n") - 1 104 | link_dict().add( 105 | file_name, 106 | file_line_number, 107 | "custom_text_in_diagram_tab", 108 | number_of_new_lines, 109 | reference_to_reset_action_custom_text, 110 | ) 111 | file_line_number += number_of_new_lines 112 | 113 | architecture += " elsif rising_edge(" + main_window.clock_signal_name.get() + ") then\n" 114 | link_dict().add(file_name, file_line_number, "Control-Tab", 1, "reset_and_clock_signal_name") 115 | file_line_number += 1 116 | 117 | reference_to_global_actions_before_custom_text, global_actions_before = ( 118 | hdl_generation_library.create_global_actions_before() 119 | ) 120 | if global_actions_before != "": 121 | global_actions_before = "-- Global Actions before:\n" + global_actions_before 122 | architecture += hdl_generation_library.indent_text_by_the_given_number_of_tabs(3, global_actions_before) 123 | # global_actions_before starts always with "-- Global Actions before:", which is not a line entered by the user, 124 | # and therefore cannot be linked: 125 | file_line_number += 1 126 | number_of_new_lines = global_actions_before.count("\n") - 1 127 | link_dict().add( 128 | file_name, 129 | file_line_number, 130 | "custom_text_in_diagram_tab", 131 | number_of_new_lines, 132 | reference_to_global_actions_before_custom_text, 133 | ) 134 | file_line_number += number_of_new_lines 135 | 136 | architecture += " -- State Machine:\n" 137 | architecture += " case state is\n" 138 | file_line_number += 2 139 | transition_specifications = hdl_generation_library.extract_transition_specifications_from_the_graph( 140 | state_tag_list_sorted 141 | ) 142 | state_sequence, file_line_number = hdl_generation_architecture_state_sequence.create_vhdl_for_the_state_sequence( 143 | transition_specifications, file_name, file_line_number 144 | ) 145 | architecture += hdl_generation_library.indent_text_by_the_given_number_of_tabs(4, state_sequence) 146 | architecture += " end case;\n" 147 | file_line_number += 1 148 | 149 | reference_to_global_actions_after_custom_text, global_actions_after = ( 150 | hdl_generation_library.create_global_actions_after() 151 | ) 152 | if global_actions_after != "": 153 | global_actions_after = "-- Global Actions after:\n" + global_actions_after 154 | architecture += hdl_generation_library.indent_text_by_the_given_number_of_tabs(3, global_actions_after) 155 | # global_actions_before starts always with "-- Global Actions after:", which is not a line entered by the user, 156 | # and therefore cannot be linked: 157 | file_line_number += 1 158 | number_of_new_lines = global_actions_after.count("\n") - 1 159 | link_dict().add( 160 | file_name, 161 | file_line_number, 162 | "custom_text_in_diagram_tab", 163 | number_of_new_lines, 164 | reference_to_global_actions_after_custom_text, 165 | ) 166 | file_line_number += number_of_new_lines 167 | 168 | architecture += " end if;\n" 169 | architecture += " end process;\n" 170 | file_line_number += 2 171 | state_actions_process, file_line_number = hdl_generation_architecture_state_actions.create_state_action_process( 172 | file_name, file_line_number, state_tag_list_sorted 173 | ) 174 | architecture += hdl_generation_library.indent_text_by_the_given_number_of_tabs(1, state_actions_process) 175 | 176 | reference_to_concurrent_actions_custom_text, concurrent_actions = hdl_generation_library.create_concurrent_actions() 177 | if concurrent_actions != "": 178 | concurrent_actions = "-- Global Actions combinatorial:\n" + concurrent_actions 179 | architecture += hdl_generation_library.indent_text_by_the_given_number_of_tabs(1, concurrent_actions) 180 | # concurrent_actions starts always with "-- Global Actions combinatorial:", which is not a line entered by 181 | # the user, and therefore cannot be linked: 182 | file_line_number += 1 183 | number_of_new_lines = concurrent_actions.count("\n") - 1 184 | link_dict().add( 185 | file_name, 186 | file_line_number, 187 | "custom_text_in_diagram_tab", 188 | number_of_new_lines, 189 | reference_to_concurrent_actions_custom_text, 190 | ) 191 | file_line_number += number_of_new_lines 192 | 193 | architecture += "end architecture;\n" 194 | file_line_number += 1 195 | return architecture 196 | 197 | 198 | def _create_type_definition_for_the_state_signal(state_tag_list_sorted) -> None: 199 | list_of_all_state_names = [ 200 | main_window.canvas.itemcget(state_tag + "_name", "text") for state_tag in state_tag_list_sorted 201 | ] 202 | if list_of_all_state_names != []: 203 | type_definition = "type t_state is (" 204 | list_of_all_state_names_reduced_by_last_entry = list_of_all_state_names[:-1] 205 | state_counter = 0 206 | for state_name in list_of_all_state_names_reduced_by_last_entry: 207 | type_definition += state_name + ", " 208 | state_counter += 1 209 | if state_counter == 10: 210 | state_counter = 0 211 | type_definition = type_definition[:-1] 212 | type_definition += "\n" 213 | type_definition += list_of_all_state_names[-1] + ");\n" 214 | return type_definition 215 | return "type t_state is ();\n" 216 | --------------------------------------------------------------------------------