digsim-logic-simulator 0.22.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. digsim/__init__.py +6 -0
  2. digsim/app/__main__.py +12 -0
  3. digsim/app/cli.py +68 -0
  4. digsim/app/gui/__init__.py +6 -0
  5. digsim/app/gui/_circuit_area.py +468 -0
  6. digsim/app/gui/_component_selection.py +154 -0
  7. digsim/app/gui/_main_window.py +163 -0
  8. digsim/app/gui/_top_bar.py +339 -0
  9. digsim/app/gui/_utils.py +26 -0
  10. digsim/app/gui/_warning_dialog.py +46 -0
  11. digsim/app/gui_objects/__init__.py +7 -0
  12. digsim/app/gui_objects/_bus_bit_object.py +94 -0
  13. digsim/app/gui_objects/_buzzer_object.py +97 -0
  14. digsim/app/gui_objects/_component_context_menu.py +79 -0
  15. digsim/app/gui_objects/_component_object.py +374 -0
  16. digsim/app/gui_objects/_component_port_item.py +63 -0
  17. digsim/app/gui_objects/_dip_switch_object.py +104 -0
  18. digsim/app/gui_objects/_gui_note_object.py +80 -0
  19. digsim/app/gui_objects/_gui_object_factory.py +80 -0
  20. digsim/app/gui_objects/_hexdigit_object.py +53 -0
  21. digsim/app/gui_objects/_image_objects.py +239 -0
  22. digsim/app/gui_objects/_label_object.py +97 -0
  23. digsim/app/gui_objects/_logic_analyzer_object.py +86 -0
  24. digsim/app/gui_objects/_seven_segment_object.py +131 -0
  25. digsim/app/gui_objects/_shortcut_objects.py +82 -0
  26. digsim/app/gui_objects/_yosys_object.py +32 -0
  27. digsim/app/gui_objects/images/AND.png +0 -0
  28. digsim/app/gui_objects/images/Analyzer.png +0 -0
  29. digsim/app/gui_objects/images/BUF.png +0 -0
  30. digsim/app/gui_objects/images/Buzzer.png +0 -0
  31. digsim/app/gui_objects/images/Clock.png +0 -0
  32. digsim/app/gui_objects/images/DFF.png +0 -0
  33. digsim/app/gui_objects/images/DIP_SWITCH.png +0 -0
  34. digsim/app/gui_objects/images/FlipFlop.png +0 -0
  35. digsim/app/gui_objects/images/IC.png +0 -0
  36. digsim/app/gui_objects/images/LED_OFF.png +0 -0
  37. digsim/app/gui_objects/images/LED_ON.png +0 -0
  38. digsim/app/gui_objects/images/MUX.png +0 -0
  39. digsim/app/gui_objects/images/NAND.png +0 -0
  40. digsim/app/gui_objects/images/NOR.png +0 -0
  41. digsim/app/gui_objects/images/NOT.png +0 -0
  42. digsim/app/gui_objects/images/ONE.png +0 -0
  43. digsim/app/gui_objects/images/OR.png +0 -0
  44. digsim/app/gui_objects/images/PB.png +0 -0
  45. digsim/app/gui_objects/images/Switch_OFF.png +0 -0
  46. digsim/app/gui_objects/images/Switch_ON.png +0 -0
  47. digsim/app/gui_objects/images/XNOR.png +0 -0
  48. digsim/app/gui_objects/images/XOR.png +0 -0
  49. digsim/app/gui_objects/images/YOSYS.png +0 -0
  50. digsim/app/gui_objects/images/ZERO.png +0 -0
  51. digsim/app/images/app_icon.png +0 -0
  52. digsim/app/model/__init__.py +6 -0
  53. digsim/app/model/_model.py +210 -0
  54. digsim/app/model/_model_components.py +162 -0
  55. digsim/app/model/_model_new_wire.py +57 -0
  56. digsim/app/model/_model_objects.py +155 -0
  57. digsim/app/model/_model_settings.py +35 -0
  58. digsim/app/model/_model_shortcuts.py +72 -0
  59. digsim/app/settings/__init__.py +8 -0
  60. digsim/app/settings/_component_settings.py +415 -0
  61. digsim/app/settings/_gui_settings.py +71 -0
  62. digsim/app/settings/_shortcut_dialog.py +39 -0
  63. digsim/circuit/__init__.py +7 -0
  64. digsim/circuit/_circuit.py +329 -0
  65. digsim/circuit/_waves_writer.py +61 -0
  66. digsim/circuit/components/__init__.py +26 -0
  67. digsim/circuit/components/_bus_bits.py +68 -0
  68. digsim/circuit/components/_button.py +44 -0
  69. digsim/circuit/components/_buzzer.py +45 -0
  70. digsim/circuit/components/_clock.py +54 -0
  71. digsim/circuit/components/_dip_switch.py +73 -0
  72. digsim/circuit/components/_flip_flops.py +99 -0
  73. digsim/circuit/components/_gates.py +246 -0
  74. digsim/circuit/components/_hexdigit.py +82 -0
  75. digsim/circuit/components/_ic.py +36 -0
  76. digsim/circuit/components/_label_wire.py +167 -0
  77. digsim/circuit/components/_led.py +18 -0
  78. digsim/circuit/components/_logic_analyzer.py +60 -0
  79. digsim/circuit/components/_mem64kbyte.py +42 -0
  80. digsim/circuit/components/_memstdout.py +37 -0
  81. digsim/circuit/components/_note.py +25 -0
  82. digsim/circuit/components/_on_off_switch.py +54 -0
  83. digsim/circuit/components/_seven_segment.py +28 -0
  84. digsim/circuit/components/_static_level.py +28 -0
  85. digsim/circuit/components/_static_value.py +44 -0
  86. digsim/circuit/components/_yosys_atoms.py +1353 -0
  87. digsim/circuit/components/_yosys_component.py +232 -0
  88. digsim/circuit/components/atoms/__init__.py +23 -0
  89. digsim/circuit/components/atoms/_component.py +280 -0
  90. digsim/circuit/components/atoms/_digsim_exception.py +8 -0
  91. digsim/circuit/components/atoms/_port.py +398 -0
  92. digsim/circuit/components/ic/74162.json +1331 -0
  93. digsim/circuit/components/ic/7448.json +834 -0
  94. digsim/storage_model/__init__.py +7 -0
  95. digsim/storage_model/_app.py +58 -0
  96. digsim/storage_model/_circuit.py +126 -0
  97. digsim/synth/__init__.py +6 -0
  98. digsim/synth/__main__.py +67 -0
  99. digsim/synth/_synthesis.py +156 -0
  100. digsim/utils/__init__.py +6 -0
  101. digsim/utils/_yosys_netlist.py +134 -0
  102. digsim_logic_simulator-0.22.0.dist-info/METADATA +140 -0
  103. digsim_logic_simulator-0.22.0.dist-info/RECORD +107 -0
  104. digsim_logic_simulator-0.22.0.dist-info/WHEEL +5 -0
  105. digsim_logic_simulator-0.22.0.dist-info/entry_points.txt +2 -0
  106. digsim_logic_simulator-0.22.0.dist-info/licenses/LICENSE.md +32 -0
  107. digsim_logic_simulator-0.22.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,7 @@
1
+ # Copyright (c) Fredrik Andersson, 2024
2
+ # All rights reserved
3
+
4
+ """All classes within digsim.storage_model namespace"""
5
+
6
+ from ._app import AppFileDataClass, GuiPositionDataClass, ModelDataClass
7
+ from ._circuit import CircuitDataClass, CircuitFileDataClass, ComponentDataClass, WireDataClass
@@ -0,0 +1,58 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """
5
+ Module that handles the dataclasses for application
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ from dataclasses import asdict
12
+ from typing import Any
13
+
14
+ from pydantic import Field
15
+ from pydantic.dataclasses import dataclass
16
+
17
+ from ._circuit import CircuitDataClass
18
+
19
+
20
+ @dataclass
21
+ class GuiPositionDataClass:
22
+ x: int = 100
23
+ y: int = 100
24
+ z: int = 0
25
+
26
+
27
+ @dataclass
28
+ class AppFileDataClass:
29
+ circuit: CircuitDataClass
30
+ gui: dict[str, GuiPositionDataClass] = Field(default_factory=dict)
31
+ shortcuts: dict[str, str] = Field(default_factory=dict)
32
+ settings: dict[str, Any] = Field(default_factory=dict)
33
+
34
+ @staticmethod
35
+ def load(filename):
36
+ try:
37
+ with open(filename, mode="r", encoding="utf-8") as json_file:
38
+ app_filedata_class = AppFileDataClass(**json.load(json_file))
39
+ except json.JSONDecodeError as exc:
40
+ raise ValueError(f"Malformed JSON file: {filename} - {exc}") from exc
41
+ except FileNotFoundError as exc:
42
+ raise FileNotFoundError(f"File not found: {filename}") from exc
43
+ return app_filedata_class
44
+
45
+ def save(self, filename):
46
+ json_object = json.dumps(asdict(self), indent=4)
47
+ with open(filename, mode="w", encoding="utf-8") as json_file:
48
+ json_file.write(json_object)
49
+
50
+
51
+ @dataclass
52
+ class ModelDataClass:
53
+ circuit: CircuitDataClass
54
+ gui: dict[str, GuiPositionDataClass]
55
+
56
+ @staticmethod
57
+ def from_app_file_dc(app_file_dc):
58
+ return ModelDataClass(circuit=app_file_dc.circuit, gui=app_file_dc.gui)
@@ -0,0 +1,126 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """
5
+ Module that handles the dataclasses for circuit load/save
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import importlib
11
+ import json
12
+ from dataclasses import asdict
13
+
14
+ from pydantic import Field
15
+ from pydantic.dataclasses import dataclass
16
+
17
+
18
+ @dataclass
19
+ class WireDataClass:
20
+ src: str
21
+ dst: str
22
+
23
+ def connect(self, circuit):
24
+ src_comp_name, src_port_name = self.src.split(".")
25
+ dst_comp_name, dst_port_name = self.dst.split(".")
26
+ src_comp = circuit.get_component(src_comp_name)
27
+ dst_comp = circuit.get_component(dst_comp_name)
28
+ src_comp.port(src_port_name).wire = dst_comp.port(dst_port_name)
29
+
30
+ @classmethod
31
+ def list_from_port(cls, src_port):
32
+ wires = []
33
+ for port in src_port.wired_ports:
34
+ # Only add port on top-level components
35
+ if port.parent().is_toplevel():
36
+ wires.append(
37
+ WireDataClass(
38
+ src=f"{src_port.parent().name()}.{src_port.name()}",
39
+ dst=f"{port.parent().name()}.{port.name()}",
40
+ )
41
+ )
42
+ return wires
43
+
44
+
45
+ @dataclass
46
+ class ComponentDataClass:
47
+ """Component data class"""
48
+
49
+ name: str
50
+ type: str
51
+ display_name: str = Field(default="")
52
+ settings: dict = Field(default_factory=dict)
53
+
54
+ def create(self, circuit):
55
+ """Factory: Create a component from a dict"""
56
+ if "path" in self.settings:
57
+ self.settings["path"] = circuit.load_path(self.settings["path"])
58
+
59
+ py_module_name = ".".join(self.type.split(".")[0:-1])
60
+ py_class_name = self.type.split(".")[-1]
61
+
62
+ module = importlib.import_module(py_module_name)
63
+ class_ = getattr(module, py_class_name)
64
+ component = class_(circuit=circuit, **self.settings)
65
+ component.set_name(self.name)
66
+ if self.display_name is not None:
67
+ component.set_display_name(self.display_name)
68
+ return component
69
+
70
+ @staticmethod
71
+ def from_component(component):
72
+ """Return the component information as a dict, used when storing a circuit"""
73
+ module_split = type(component).__module__.split(".")
74
+ type_str = ""
75
+ for module in module_split:
76
+ if not module.startswith("_"):
77
+ type_str += f"{module}."
78
+ type_str += type(component).__name__
79
+
80
+ return ComponentDataClass(
81
+ name=component.name(),
82
+ display_name=component.display_name(),
83
+ type=type_str,
84
+ settings=component.settings_to_dict(),
85
+ )
86
+
87
+
88
+ @dataclass
89
+ class CircuitDataClass:
90
+ name: str = "unnamed"
91
+ components: list[ComponentDataClass] = Field(default_factory=list)
92
+ wires: list[WireDataClass] = Field(default_factory=list)
93
+
94
+ @staticmethod
95
+ def from_circuit(circuit):
96
+ dc = CircuitDataClass(name=circuit.name)
97
+ toplevel_components = circuit.get_toplevel_components()
98
+ for comp in toplevel_components:
99
+ dc.components.append(ComponentDataClass.from_component(comp))
100
+
101
+ for comp in toplevel_components:
102
+ for port in comp.ports:
103
+ dc.wires.extend(WireDataClass.list_from_port(port))
104
+
105
+ return dc
106
+
107
+
108
+ @dataclass
109
+ class CircuitFileDataClass:
110
+ circuit: CircuitDataClass
111
+
112
+ @staticmethod
113
+ def load(filename):
114
+ try:
115
+ with open(filename, mode="r", encoding="utf-8") as json_file:
116
+ dc = CircuitFileDataClass(**json.load(json_file))
117
+ except json.JSONDecodeError as exc:
118
+ raise ValueError(f"Malformed JSON file: {filename} - {exc}") from exc
119
+ except FileNotFoundError as exc:
120
+ raise FileNotFoundError(f"File not found: {filename}") from exc
121
+ return dc
122
+
123
+ def save(self, filename):
124
+ json_object = json.dumps(asdict(self), indent=4)
125
+ with open(filename, mode="w", encoding="utf-8") as json_file:
126
+ json_file.write(json_object)
@@ -0,0 +1,6 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """All classes within digsim.synth namespace"""
5
+
6
+ from ._synthesis import Synthesis, SynthesisException # noqa: F401
@@ -0,0 +1,67 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """The main class module of the digsim.synth namespace"""
5
+
6
+ import argparse
7
+ import sys
8
+ import time
9
+
10
+ from . import Synthesis, SynthesisException
11
+
12
+
13
+ def _synth_modules(args):
14
+ print("Synthesis started...")
15
+ for infile in args.input_files:
16
+ print(f" - Reading {infile}")
17
+ print(f"Generating {args.output_file}...")
18
+ start_time = time.monotonic()
19
+ synthesis = Synthesis(args.input_files, args.top)
20
+ try:
21
+ synthesis.synth_to_json_file(args.output_file, silent=args.silent)
22
+ print(f"Synthesis complete in {time.monotonic() - start_time:.2f}s")
23
+ except SynthesisException as exc:
24
+ print(f"ERROR: {str(exc)}")
25
+ return -1
26
+ return 0
27
+
28
+
29
+ def _list_modules(args):
30
+ try:
31
+ modules = Synthesis.list_modules(args.input_files)
32
+ print("Modules:")
33
+ print("========")
34
+ for idx, module in enumerate(modules):
35
+ print(f"{idx}: {module}")
36
+ print("========")
37
+ except SynthesisException as exc:
38
+ print(f"ERROR: {str(exc)}")
39
+ return -1
40
+ return 0
41
+
42
+
43
+ if __name__ == "__main__":
44
+ parser = argparse.ArgumentParser("Yosys synthesizer helper")
45
+ subparser = parser.add_subparsers(required=True)
46
+ synth_parser = subparser.add_parser("synth")
47
+ synth_parser.add_argument(
48
+ "--input-files", "-i", type=str, nargs="+", required=True, help="The verilog input files"
49
+ )
50
+ synth_parser.add_argument(
51
+ "--output-file", "-o", type=str, required=True, help="The json output file"
52
+ )
53
+ synth_parser.add_argument(
54
+ "--top", "-t", type=str, required=True, help="The verilog top module"
55
+ )
56
+ synth_parser.add_argument(
57
+ "--silent", "-s", action="store_true", help="Silent the yosys output"
58
+ )
59
+ synth_parser.set_defaults(func=_synth_modules)
60
+ list_parser = subparser.add_parser("list")
61
+ list_parser.add_argument(
62
+ "--input-files", "-i", type=str, nargs="+", required=True, help="The verilog input files"
63
+ )
64
+ list_parser.set_defaults(func=_list_modules)
65
+ arguments = parser.parse_args()
66
+ returncode = arguments.func(arguments)
67
+ sys.exit(returncode)
@@ -0,0 +1,156 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """Helper module for yosys synthesis"""
5
+
6
+ import json
7
+ import pathlib
8
+ import shutil
9
+ import site
10
+ import sys
11
+
12
+ import pexpect
13
+ import pexpect.popen_spawn
14
+
15
+ from digsim.circuit.components.atoms import DigsimException
16
+
17
+
18
+ class SynthesisException(DigsimException):
19
+ """Exception class for yosys synthesis"""
20
+
21
+
22
+ class Synthesis:
23
+ """Helper class for yosys synthesis"""
24
+
25
+ @classmethod
26
+ def _pexpect_wait_for_prompt(cls, pexp):
27
+ index = pexp.expect(["yosys>", pexpect.EOF])
28
+ before_lines = pexp.before.decode("utf8").replace("\r", "").split("\n")
29
+ if index == 0:
30
+ pass
31
+ elif index == 1:
32
+ # Unexpected EOF means ERROR
33
+ errorline = "ERROR"
34
+ for line in before_lines:
35
+ if "ERROR" in line:
36
+ errorline = line
37
+ break
38
+ raise SynthesisException(errorline)
39
+
40
+ # Remove escape sequence in output
41
+ out_lines = []
42
+ for line in before_lines:
43
+ if line.startswith("\x1b"):
44
+ continue
45
+ out_lines.append(line)
46
+ return out_lines
47
+
48
+ @staticmethod
49
+ def _find_win_yowasp_yosys_binary():
50
+ try:
51
+ # Use getusersitepackages if this is present, as it ensures that the
52
+ # value is initialised properly.
53
+ user_site = site.getusersitepackages()
54
+ except AttributeError:
55
+ user_site = site.USER_SITE
56
+ scripts_path = pathlib.Path(user_site).parent / "Scripts"
57
+ yowasp_yosys_path = scripts_path / "yowasp-yosys.exe"
58
+ if yowasp_yosys_path.is_file():
59
+ return str(yowasp_yosys_path)
60
+ return None
61
+
62
+ @classmethod
63
+ def _pexpect_spawn_yosys(cls):
64
+ # Find linux binary
65
+ yosys_exe = shutil.which("yosys") or shutil.which("yowasp-yosys")
66
+
67
+ # No binary found
68
+ if yosys_exe is None:
69
+ # Try to find windows binary
70
+ yosys_exe = cls._find_win_yowasp_yosys_binary()
71
+
72
+ if yosys_exe is None:
73
+ raise SynthesisException("Yosys executable not found")
74
+
75
+ if sys.platform == "win32":
76
+ return pexpect.popen_spawn.PopenSpawn(yosys_exe)
77
+
78
+ return pexpect.spawn(yosys_exe)
79
+
80
+ @classmethod
81
+ def list_modules(cls, verilog_files):
82
+ """List available modules in verilog files"""
83
+ if isinstance(verilog_files, str):
84
+ verilog_files = [verilog_files]
85
+
86
+ pexp = cls._pexpect_spawn_yosys()
87
+ cls._pexpect_wait_for_prompt(pexp)
88
+ pexp.sendline(f"read -sv {' '.join(verilog_files)}")
89
+ cls._pexpect_wait_for_prompt(pexp)
90
+ pexp.sendline("ls")
91
+ pexp.expect("\n")
92
+ ls_response = cls._pexpect_wait_for_prompt(pexp)
93
+ pexp.sendline("exit")
94
+
95
+ modules = []
96
+ for line in ls_response:
97
+ if len(line) == 0:
98
+ continue
99
+ if "modules:" in line:
100
+ continue
101
+ modules.append(line.replace("$abstract\\", "").strip())
102
+ return modules
103
+
104
+ def __init__(self, verilog_files, verilog_top_module):
105
+ if isinstance(verilog_files, str):
106
+ self._verilog_files = [verilog_files]
107
+ else:
108
+ self._verilog_files = verilog_files
109
+ self._verilog_top_module = verilog_top_module
110
+ self._yosys_log = []
111
+
112
+ def synth_to_json(self, silent=False):
113
+ """Execute yosys with generated synthesis script"""
114
+ script = f"read -sv {' '.join(self._verilog_files)}; "
115
+ script += f"hierarchy -top {self._verilog_top_module}; "
116
+ script += "proc; flatten; "
117
+ script += "memory_dff; "
118
+ script += "proc; opt; techmap; opt; "
119
+ script += f"synth -noabc -top {self._verilog_top_module}; "
120
+
121
+ pexp = self._pexpect_spawn_yosys()
122
+ self._pexpect_wait_for_prompt(pexp)
123
+ pexp.sendline(script)
124
+ yosys_log = self._pexpect_wait_for_prompt(pexp)
125
+ for line in yosys_log:
126
+ self._yosys_log.append(line)
127
+ if silent:
128
+ continue
129
+ print("Yosys:", line)
130
+ pexp.sendline("write_json")
131
+ pexp.expect("Executing JSON backend.")
132
+ json_lines = self._pexpect_wait_for_prompt(pexp)
133
+ pexp.sendline("exit")
134
+
135
+ return "\n".join(json_lines)
136
+
137
+ def synth_to_dict(self, silent=False):
138
+ """Execute yosys with generated synthesis script and return python dict"""
139
+ yosys_json = self.synth_to_json(silent)
140
+ try:
141
+ netlist_dict = json.loads(yosys_json)
142
+ except json.JSONDecodeError as exc:
143
+ raise SynthesisException(f"Malformed JSON output from Yosys: {exc}") from exc
144
+ return netlist_dict
145
+
146
+ def synth_to_json_file(self, filename, silent=False):
147
+ """Execute yosys with generated synthesis script and write to file"""
148
+ yosys_json = self.synth_to_json(silent)
149
+ if yosys_json is None:
150
+ raise SynthesisException("Yosys synthesis failed")
151
+ with open(filename, mode="w", encoding="utf-8") as json_file:
152
+ json_file.write(yosys_json)
153
+
154
+ def get_log(self):
155
+ """Get the yosys output"""
156
+ return self._yosys_log
@@ -0,0 +1,6 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """All classes within digsim.utils namespace"""
5
+
6
+ from ._yosys_netlist import YosysCell, YosysModule, YosysNetlist # noqa: F401
@@ -0,0 +1,134 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """
5
+ Module with classes to parse a yosys netlist
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Literal, Optional, Union
11
+
12
+ from pydantic import Field
13
+ from pydantic.dataclasses import dataclass
14
+
15
+
16
+ BIT_TYPE = list[Union[int, Literal["X"], Literal["x"], Literal["0"], Literal["1"]]]
17
+
18
+
19
+ @dataclass
20
+ class NetPort:
21
+ parent: Union[YosysModule, YosysCell]
22
+ parent_name: str
23
+ name: str
24
+ bit_index: Optional[int] = None
25
+
26
+
27
+ @dataclass
28
+ class Nets:
29
+ source: dict[int, NetPort] = Field(default_factory=dict)
30
+ sinks: dict[int, list[NetPort]] = Field(default_factory=dict)
31
+
32
+
33
+ @dataclass
34
+ class YosysPort:
35
+ direction: str
36
+ bits: BIT_TYPE
37
+
38
+ @property
39
+ def is_output(self):
40
+ return self.direction == "output"
41
+
42
+ def is_same(self, compare_port):
43
+ return (compare_port.direction == self.direction) and (
44
+ len(compare_port.bits) == len(self.bits)
45
+ )
46
+
47
+
48
+ @dataclass
49
+ class YosysCell:
50
+ type: str
51
+ port_directions: dict[str, str] = Field(default_factory=dict)
52
+ connections: dict[str, BIT_TYPE] = Field(default_factory=dict)
53
+ hide_name: int = 0
54
+ parameters: dict[str, Any] = Field(default_factory=dict)
55
+ attributes: dict[str, Any] = Field(default_factory=dict)
56
+
57
+ def get_nets(self, name, nets):
58
+ for port_name, net_list in self.connections.items():
59
+ if not net_list:
60
+ # Handle empty net_list, e.g., by skipping or raising an error
61
+ continue
62
+ net = net_list[0]
63
+ port = NetPort(parent=self, parent_name=name, name=port_name)
64
+ if self.port_directions[port_name] == "input":
65
+ if net not in nets.sinks:
66
+ nets.sinks[net] = []
67
+ nets.sinks[net].append(port)
68
+ else:
69
+ nets.source[net] = port
70
+
71
+ def component_name(self, name):
72
+ """Return a friendly name for a netlist cell"""
73
+ return f"{name.split('$')[-1]}_{self.component_type()}"
74
+
75
+ def component_type(self):
76
+ """Return a friendly type for a netlist cell"""
77
+ return f"_{self.type[2:-1]}_"
78
+
79
+
80
+ @dataclass
81
+ class YosysNetName:
82
+ bits: BIT_TYPE
83
+ attributes: dict[str, Any] = Field(default_factory=dict)
84
+ hide_name: int = 0
85
+
86
+
87
+ @dataclass
88
+ class YosysModule:
89
+ attributes: dict[str, Any] = Field(default_factory=dict)
90
+ parameter_default_values: dict[str, Any] = Field(default_factory=dict)
91
+ ports: dict[str, YosysPort] = Field(default_factory=dict)
92
+ cells: dict[str, YosysCell] = Field(default_factory=dict)
93
+ netnames: dict[str, YosysNetName] = Field(default_factory=dict)
94
+
95
+ def is_same_interface(self, netlist):
96
+ is_same = True
97
+ if len(netlist.ports) == len(self.ports):
98
+ for netlist_port_name, netlist_port in netlist.ports.items():
99
+ module_port = self.ports.get(netlist_port_name)
100
+ if module_port is None or not module_port.is_same(netlist_port):
101
+ # Port does not exist or has a different bitwidth
102
+ is_same = False
103
+ break
104
+ else:
105
+ # The number of ports does not match
106
+ is_same = False
107
+ return is_same
108
+
109
+ def get_nets(self):
110
+ nets = Nets()
111
+
112
+ for port_name, port_item in self.ports.items():
113
+ for bit_index, net in enumerate(port_item.bits):
114
+ port = NetPort(parent=self, parent_name="top", name=port_name, bit_index=bit_index)
115
+ if port_item.is_output:
116
+ if net not in nets.sinks:
117
+ nets.sinks[net] = []
118
+ nets.sinks[net].append(port)
119
+ else:
120
+ nets.source[net] = port
121
+
122
+ for cell_name, cell in self.cells.items():
123
+ cell.get_nets(cell_name, nets)
124
+
125
+ return nets
126
+
127
+
128
+ @dataclass
129
+ class YosysNetlist:
130
+ creator: Optional[str] = None
131
+ modules: dict[str, YosysModule] = Field(default_factory=dict)
132
+
133
+ def get_modules(self):
134
+ return self.modules