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,63 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """A component port graphics item"""
5
+
6
+ from PySide6.QtCore import QRect, Qt
7
+ from PySide6.QtGui import QBrush, QPen
8
+ from PySide6.QtWidgets import QGraphicsRectItem
9
+
10
+ from digsim.circuit.components.atoms import PortConnectionError
11
+
12
+
13
+ class PortGraphicsItem(QGraphicsRectItem):
14
+ """A port graphics item"""
15
+
16
+ def __init__(self, app_model, parent, port):
17
+ super().__init__(QRect(0, 0, 0, 0), parent)
18
+ self._app_model = app_model
19
+ self._port = port
20
+ self.setPen(QPen(Qt.black))
21
+ self.setBrush(Qt.SolidPattern)
22
+ self.setBrush(QBrush(Qt.gray))
23
+ self.setAcceptHoverEvents(True)
24
+
25
+ def _repaint(self):
26
+ """Make scene repaint for component update"""
27
+ self._app_model.sig_repaint.emit()
28
+
29
+ def mousePressEvent(self, _):
30
+ """QT event callback function"""
31
+ if self._app_model.is_running:
32
+ return
33
+ if self._app_model.objects.new_wire.ongoing():
34
+ try:
35
+ self._app_model.objects.new_wire.end(self._port.parent(), self._port.name())
36
+ except PortConnectionError as exc:
37
+ self._app_model.objects.new_wire.abort()
38
+ self._app_model.sig_error.emit(str(exc))
39
+ self._repaint()
40
+ else:
41
+ self._app_model.objects.new_wire.start(self._port.parent(), self._port.name())
42
+
43
+ def hoverEnterEvent(self, _):
44
+ """QT event callback function"""
45
+ if self._app_model.is_running:
46
+ return
47
+ self.setBrush(QBrush(Qt.black))
48
+ self.setCursor(Qt.CrossCursor)
49
+
50
+ def hoverLeaveEvent(self, _):
51
+ """QT event callback function"""
52
+ if self._app_model.is_running:
53
+ return
54
+ self.setBrush(QBrush(Qt.gray))
55
+ self.setCursor(Qt.ArrowCursor)
56
+
57
+ def portParentRect(self):
58
+ """return the parent rect"""
59
+ return self.parentItem().rect().translated(self.parentItem().pos())
60
+
61
+ def portPos(self):
62
+ """return the parent position"""
63
+ return self.parentItem().pos() + self.rect().center()
@@ -0,0 +1,104 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """A hexdigit component placed in the GUI"""
5
+
6
+ from PySide6.QtCore import QPoint, QRect, Qt
7
+ from PySide6.QtGui import QFont, QPen
8
+
9
+ from ._image_objects import ImageObject
10
+
11
+
12
+ class DipSwitchObject(ImageObject):
13
+ """The class for a bus/bit component placed in the GUI"""
14
+
15
+ IMAGE_FILENAME = "images/DIP_SWITCH.png"
16
+ DIP_SWITCH_WIDTH = 20
17
+ DIP_SWITCH_HEIGHT = 10
18
+
19
+ _DIP_SWITCH_FONT = QFont("Arial", 8)
20
+
21
+ def __init__(self, app_model, component, xpos, ypos):
22
+ super().__init__(app_model, component, xpos, ypos, port_distance=self.DIP_SWITCH_HEIGHT)
23
+ self.width = 2.5 * self.DIP_SWITCH_WIDTH
24
+ self.height = self.component.bits() * self.DIP_SWITCH_HEIGHT
25
+ self._rects = []
26
+ self.update_ports()
27
+
28
+ def update_ports(self):
29
+ super().update_ports()
30
+ self._rects = []
31
+ for idx in range(0, self.component.bits()):
32
+ port_pos = self.get_port_pos(f"{idx}")
33
+ self._rects.append(
34
+ QRect(
35
+ self.object_pos.x() + self.rect().width() / 2 - self.DIP_SWITCH_WIDTH / 2,
36
+ port_pos.y() - self.DIP_SWITCH_HEIGHT / 2,
37
+ self.DIP_SWITCH_WIDTH,
38
+ self.DIP_SWITCH_HEIGHT,
39
+ )
40
+ )
41
+
42
+ def mouse_position(self, pos):
43
+ """update component according to on mouse move"""
44
+ select = None
45
+ pos = pos - self.pos()
46
+ for idx, rect in enumerate(self._rects):
47
+ if pos.x() > rect.x() and pos.x() < (rect.x() + rect.width()):
48
+ if pos.y() > rect.y() and pos.y() < (rect.y() + rect.height()):
49
+ select = idx
50
+ break
51
+ self.component.select(select)
52
+ self.repaint()
53
+
54
+ def single_click_action(self):
55
+ self.component.toggle()
56
+ self.repaint()
57
+
58
+ def _paint_dip_switch(self, painter):
59
+ pen = QPen()
60
+ pen.setColor(Qt.black)
61
+ pen.setWidth(1)
62
+ painter.setPen(pen)
63
+ select_id = self.component.selected()
64
+ for idx, rect in enumerate(self._rects):
65
+ painter.setBrush(Qt.darkGray)
66
+ painter.drawRect(rect)
67
+ if select_id == idx:
68
+ painter.setBrush(Qt.green)
69
+ else:
70
+ painter.setBrush(Qt.white)
71
+ if self.component.is_set(idx):
72
+ painter.drawRect(
73
+ rect.x() + rect.width() / 2 + 1,
74
+ rect.y() + 1,
75
+ rect.width() / 2 - 2,
76
+ rect.height() - 2,
77
+ )
78
+ else:
79
+ painter.drawRect(
80
+ rect.x() + 1, rect.y() + 1, rect.width() / 2 - 2, rect.height() - 2
81
+ )
82
+ painter.setFont(self._DIP_SWITCH_FONT)
83
+ painter.setPen(Qt.white)
84
+ for idx, rect in enumerate(self._rects):
85
+ port_str = f"{idx + 1}"
86
+ str_pixels_w, _ = self.get_string_metrics(port_str, self._DIP_SWITCH_FONT)
87
+ painter.drawText(
88
+ QPoint(rect.x() - str_pixels_w - 4, rect.y() + rect.height() - 1), port_str
89
+ )
90
+
91
+ _, str_h = self.get_string_metrics("ON", self._DIP_SWITCH_FONT)
92
+ painter.drawText(
93
+ self.rect().x() + self.rect().width() / 2,
94
+ self.rect().y() + self.BORDER_TO_PORT - str_h,
95
+ "ON",
96
+ )
97
+
98
+ def _portlist(self):
99
+ return self.component.outports()
100
+
101
+ def paint(self, painter, option, widget=None):
102
+ """QT function"""
103
+ self.paint_component_base(painter, color=Qt.red)
104
+ self._paint_dip_switch(painter)
@@ -0,0 +1,80 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """A GUI note object"""
5
+
6
+ from PySide6.QtCore import QPoint, QRect, Qt
7
+ from PySide6.QtGui import QFont, QFontMetrics, QPen
8
+
9
+ from ._component_object import ComponentObject
10
+
11
+
12
+ class GuiNoteObject(ComponentObject):
13
+ """The class for a note placed in the GUI"""
14
+
15
+ NOTE_BORDER = 10
16
+ NOTE_MINIMUM_WIDTH = 50
17
+ NOTE_MINIMUM_HEIGHT = 20
18
+
19
+ _NOTE_FONT = QFont("Arial", 8)
20
+ _NOTE_PEN = QPen(Qt.black)
21
+
22
+ def __init__(self, app_model, component, xpos, ypos):
23
+ super().__init__(app_model, component, xpos, ypos)
24
+ self.update_size()
25
+
26
+ def _get_lines(self):
27
+ return self.component.parameter_get("text").split("\n")
28
+
29
+ def update_size(self):
30
+ fm = QFontMetrics(self._NOTE_FONT)
31
+ width = self.NOTE_MINIMUM_WIDTH
32
+ lines = self._get_lines()
33
+ for line in lines:
34
+ str_pixels_w = fm.horizontalAdvance(line)
35
+ width = max(width, str_pixels_w)
36
+ self.width = width + 2 * self.NOTE_BORDER
37
+ self.height = max(
38
+ self.NOTE_MINIMUM_HEIGHT,
39
+ 2 * self.NOTE_BORDER + len(lines) * fm.height(),
40
+ )
41
+
42
+ def paint_component(self, painter):
43
+ """Paint note rectangle"""
44
+ fm = QFontMetrics(self._NOTE_FONT)
45
+ lines = self._get_lines()
46
+ pen = self._NOTE_PEN
47
+ if self.selected:
48
+ pen.setWidth(4)
49
+ else:
50
+ pen.setWidth(1)
51
+ painter.setPen(pen)
52
+ painter.setBrush(Qt.SolidPattern)
53
+ painter.setBrush(Qt.yellow)
54
+ painter.drawRect(self.rect())
55
+ painter.setFont(self._NOTE_FONT)
56
+ for idx, line in enumerate(lines):
57
+ painter.drawText(
58
+ self.object_pos.x() + self.NOTE_BORDER,
59
+ self.object_pos.y() + self.NOTE_BORDER + (idx + 1) * fm.height(),
60
+ line,
61
+ )
62
+
63
+ @classmethod
64
+ def paint_selectable_component(cls, painter, size, name):
65
+ note_rect = QRect(
66
+ cls.NOTE_BORDER,
67
+ cls.NOTE_BORDER,
68
+ size.width() - 2 * cls.NOTE_BORDER,
69
+ size.width() - 2 * cls.NOTE_BORDER,
70
+ )
71
+ pen = cls._NOTE_PEN
72
+ pen.setWidth(1)
73
+ painter.setPen(pen)
74
+ painter.setBrush(Qt.SolidPattern)
75
+ painter.setBrush(Qt.yellow)
76
+ painter.drawRect(note_rect)
77
+ painter.setFont(cls._NOTE_FONT)
78
+ fm = QFontMetrics(cls._NOTE_FONT)
79
+ painter.drawText(2 * cls.NOTE_BORDER, 2 * cls.NOTE_BORDER + fm.height(), "abc..")
80
+ cls.paint_selectable_component_name(painter, QPoint(0, 0), size, name)
@@ -0,0 +1,80 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """A GUI component object factory module"""
5
+
6
+ from digsim.circuit.components.atoms import DigsimException
7
+
8
+ from ._bus_bit_object import BitsBusObject, BusBitsObject
9
+ from ._buzzer_object import BuzzerObject
10
+ from ._dip_switch_object import DipSwitchObject
11
+ from ._gui_note_object import GuiNoteObject
12
+ from ._hexdigit_object import HexDigitObject
13
+ from ._image_objects import (
14
+ ImageObjectAND,
15
+ ImageObjectClock,
16
+ ImageObjectDFF,
17
+ ImageObjectFlipFlop,
18
+ ImageObjectIC,
19
+ ImageObjectLed,
20
+ ImageObjectMUX,
21
+ ImageObjectNAND,
22
+ ImageObjectNOR,
23
+ ImageObjectNOT,
24
+ ImageObjectOR,
25
+ ImageObjectStaticValue,
26
+ ImageObjectXOR,
27
+ )
28
+ from ._label_object import LabelObject
29
+ from ._logic_analyzer_object import LogicAnalyzerObject
30
+ from ._seven_segment_object import SevenSegmentObject
31
+ from ._shortcut_objects import ButtonObject, OnOffSwitchObject
32
+ from ._yosys_object import YosysObject
33
+
34
+
35
+ class ComponentObjectFactoryError(DigsimException):
36
+ """ComponentObjectFactoryError"""
37
+
38
+
39
+ CLASS_NAME_TO_COMPONENT_OBJECT = {
40
+ "AND": ImageObjectAND,
41
+ "Bus2Wires": BusBitsObject,
42
+ "Wires2Bus": BitsBusObject,
43
+ "Clock": ImageObjectClock,
44
+ "DipSwitch": DipSwitchObject,
45
+ "DFF": ImageObjectDFF,
46
+ "HexDigit": HexDigitObject,
47
+ "LabelWireIn": LabelObject,
48
+ "LabelWireOut": LabelObject,
49
+ "Led": ImageObjectLed,
50
+ "LogicAnalyzer": LogicAnalyzerObject,
51
+ "NAND": ImageObjectNAND,
52
+ "NOR": ImageObjectNOR,
53
+ "NOT": ImageObjectNOT,
54
+ "MUX": ImageObjectMUX,
55
+ "OR": ImageObjectOR,
56
+ "OnOffSwitch": OnOffSwitchObject,
57
+ "PushButton": ButtonObject,
58
+ "SevenSegment": SevenSegmentObject,
59
+ "StaticValue": ImageObjectStaticValue,
60
+ "XOR": ImageObjectXOR,
61
+ "IntegratedCircuit": ImageObjectIC,
62
+ "YosysComponent": YosysObject,
63
+ "Note": GuiNoteObject,
64
+ "FlipFlop": ImageObjectFlipFlop,
65
+ "SRFF": ImageObjectFlipFlop,
66
+ "ClockedSRFF": ImageObjectFlipFlop,
67
+ "ClockedJKFF": ImageObjectFlipFlop,
68
+ "ClockedTFF": ImageObjectFlipFlop,
69
+ "Buzzer": BuzzerObject,
70
+ }
71
+
72
+
73
+ def class_factory(component_class_name):
74
+ """A function that returns the GUI for a component class (str or class)"""
75
+
76
+ if component_class_name in CLASS_NAME_TO_COMPONENT_OBJECT:
77
+ return CLASS_NAME_TO_COMPONENT_OBJECT[component_class_name]
78
+
79
+ # Raise exception if component not found
80
+ raise ComponentObjectFactoryError(f"Unknown component '{component_class_name}'")
@@ -0,0 +1,53 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """A hexdigit component placed in the GUI"""
5
+
6
+ from ._component_object import ComponentObject
7
+ from ._seven_segment_object import SevenSegmentObject
8
+
9
+
10
+ class HexDigitObject(SevenSegmentObject):
11
+ """The class for a hexdigit component placed in the GUI"""
12
+
13
+ def __init__(self, app_model, component, xpos, ypos):
14
+ super().__init__(
15
+ app_model,
16
+ component,
17
+ xpos,
18
+ ypos,
19
+ port_distance=ComponentObject.DEFAULT_PORT_TO_PORT_DISTANCE,
20
+ )
21
+
22
+ def setup_size(self):
23
+ """Setup the size of the component"""
24
+ self.digits = self.component.get_digits()
25
+ str_pixels_w, _ = self.get_string_metrics(f"val[{4 * self.digits - 1}:0]")
26
+ self.digit_left = self.inport_x_pos() + str_pixels_w + self.PORT_TO_RECT_MARGIN
27
+ self.digit_top = self.RECT_TO_DIGIT_RECT_MARGIN
28
+ self.width = (
29
+ self.digit_left + self.digits * self.DIGIT_WIDTH + self.RECT_TO_DIGIT_RECT_MARGIN
30
+ )
31
+ self.height = 2 * self.RECT_TO_DIGIT_RECT_MARGIN + self.DIGIT_HEIGHT
32
+ self.update_ports()
33
+
34
+ def paint_component(self, painter):
35
+ self.paint_component_base(painter)
36
+ self.paint_digit_rect(
37
+ painter,
38
+ self.object_pos.x() + self.digit_left,
39
+ self.object_pos.y() + self.RECT_TO_DIGIT_RECT_MARGIN,
40
+ self.digits,
41
+ )
42
+ for digit_id in range(self.digits):
43
+ active_segments = self.component.segments(digit_id)
44
+ self.draw_digit(
45
+ painter,
46
+ self.object_pos.x() + self.digit_left + self.DIGIT_WIDTH * digit_id,
47
+ self.object_pos.y() + self.digit_top,
48
+ active_segments,
49
+ )
50
+
51
+ @classmethod
52
+ def paint_selectable_component(cls, painter, size, name):
53
+ cls.paint_selectable_digit(painter, size, name, "ABDEG")
@@ -0,0 +1,239 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """A component with an image as symbol the GUI"""
5
+
6
+ from __future__ import annotations
7
+
8
+ from pathlib import Path
9
+
10
+ from PySide6.QtCore import QPoint, Qt
11
+ from PySide6.QtGui import QFont, QPen, QPixmap
12
+
13
+ from ._component_object import ComponentObject
14
+
15
+
16
+ class ImageObject(ComponentObject):
17
+ """The class for a image component placed in the GUI"""
18
+
19
+ IMAGE_FILENAME: str | None = None
20
+ ACTIVE_IMAGE_FILENAME: str | None = None
21
+ _pixmap = None
22
+ _pixmap_active = None
23
+
24
+ def __init__(
25
+ self,
26
+ app_model,
27
+ component,
28
+ xpos,
29
+ ypos,
30
+ port_distance=ComponentObject.DEFAULT_PORT_TO_PORT_DISTANCE,
31
+ ):
32
+ super().__init__(app_model, component, xpos, ypos, port_distance=port_distance)
33
+ self._show_name = True
34
+ self.setup_size()
35
+ self.update_ports()
36
+
37
+ def show_name(self, enable):
38
+ """Enable/Disable name on image component"""
39
+ self._show_name = enable
40
+
41
+ def setup_size(self):
42
+ """Change size of component"""
43
+
44
+ @classmethod
45
+ def _get_pixmaps(cls):
46
+ """Load the pixmap at first use"""
47
+ if cls.ACTIVE_IMAGE_FILENAME is not None and cls._pixmap_active is None:
48
+ cls._pixmap_active = QPixmap(Path(__file__).parent / cls.ACTIVE_IMAGE_FILENAME)
49
+ if cls.IMAGE_FILENAME is not None and cls._pixmap is None:
50
+ cls._pixmap = QPixmap(Path(__file__).parent / cls.IMAGE_FILENAME)
51
+
52
+ def paint_component(self, painter):
53
+ self.paint_component_base(painter)
54
+ self._get_pixmaps()
55
+ xpos = self.size.width() / 2 - self._pixmap.width() / 2
56
+ ypos = self.size.height() / 2 - self._pixmap.height() / 2
57
+ self.paint_pixmap(
58
+ painter, self.object_pos.x() + xpos, self.object_pos.y() + ypos, self.component.active
59
+ )
60
+ if self._show_name:
61
+ self.paint_selectable_component_name(
62
+ painter, self.object_pos, self.size, self.component.display_name()
63
+ )
64
+
65
+ @classmethod
66
+ def paint_selectable_component(cls, painter, size, name):
67
+ cls.paint_selectable_component_name(painter, QPoint(0, 0), size, name)
68
+ cls._get_pixmaps()
69
+ xpos = size.width() / 2 - cls._pixmap.width() / 2
70
+ ypos = size.width() / 2 - cls._pixmap.height() / 2
71
+ cls.paint_pixmap(painter, xpos, ypos)
72
+
73
+ @classmethod
74
+ def paint_pixmap(cls, painter, xpos, ypos, active=False):
75
+ """Paint the pixmap"""
76
+ pixmap = cls._pixmap_active if active and cls._pixmap_active is not None else cls._pixmap
77
+ painter.drawPixmap(QPoint(xpos, ypos), pixmap)
78
+
79
+
80
+ class GateImageObject(ImageObject):
81
+ """The base class for gate components placed in the GUI"""
82
+
83
+ def __init__(self, app_model, component, xpos, ypos):
84
+ super().__init__(app_model, component, xpos, ypos)
85
+ self.paint_port_names(False)
86
+
87
+
88
+ class ImageObjectAND(GateImageObject):
89
+ """The class for a AND image component placed in the GUI"""
90
+
91
+ IMAGE_FILENAME = "images/AND.png"
92
+
93
+
94
+ class ImageObjectOR(GateImageObject):
95
+ """The class for a OR image component placed in the GUI"""
96
+
97
+ IMAGE_FILENAME = "images/OR.png"
98
+
99
+
100
+ class ImageObjectNAND(GateImageObject):
101
+ """The class for a NAND image component placed in the GUI"""
102
+
103
+ IMAGE_FILENAME = "images/NAND.png"
104
+
105
+
106
+ class ImageObjectNOR(GateImageObject):
107
+ """The class for a NOR image component placed in the GUI"""
108
+
109
+ IMAGE_FILENAME = "images/NOR.png"
110
+
111
+
112
+ class ImageObjectNOT(GateImageObject):
113
+ """The class for a NOR image component placed in the GUI"""
114
+
115
+ IMAGE_FILENAME = "images/NOT.png"
116
+
117
+
118
+ class ImageObjectXOR(GateImageObject):
119
+ """The class for a XOR image component placed in the GUI"""
120
+
121
+ IMAGE_FILENAME = "images/XOR.png"
122
+
123
+
124
+ class ImageObjectDFF(ImageObject):
125
+ """The class for a DFF image component placed in the GUI"""
126
+
127
+ IMAGE_FILENAME = "images/DFF.png"
128
+ PORT_TO_IMAGE_DIST = 20
129
+
130
+ def setup_size(self):
131
+ self._get_pixmaps()
132
+ str_pixels_w, _ = self.get_string_metrics("D")
133
+ self.width = 2 * (str_pixels_w + self.PORT_TO_IMAGE_DIST) + self._pixmap.width()
134
+
135
+
136
+ class ImageObjectFlipFlop(ImageObject):
137
+ """The class for a FlipFLop image component placed in the GUI"""
138
+
139
+ IMAGE_FILENAME = "images/FlipFlop.png"
140
+ PORT_TO_IMAGE_DIST = 20
141
+
142
+ def paint_component(self, painter):
143
+ self.paint_component_base(painter)
144
+ self.paint_component_name(painter)
145
+
146
+
147
+ class ImageObjectMUX(ImageObject):
148
+ """The class for a MUX image component placed in the GUI"""
149
+
150
+ IMAGE_FILENAME = "images/MUX.png"
151
+ PORT_TO_IMAGE_DIST = 20
152
+
153
+ def setup_size(self):
154
+ self._get_pixmaps()
155
+ if self.component.port("A").width == 1:
156
+ str_pixels_w, _ = self.get_string_metrics("A")
157
+ else:
158
+ str_pixels_w, _ = self.get_string_metrics("A[31:0]")
159
+ self.width = 2 * (str_pixels_w + self.PORT_TO_IMAGE_DIST) + self._pixmap.width()
160
+
161
+
162
+ class ImageObjectStaticValue(ImageObject):
163
+ """The class for a StaticValue image component placed in the GUI"""
164
+
165
+ IMAGE_FILENAME = "images/ZERO.png"
166
+ ACTIVE_IMAGE_FILENAME = "images/ONE.png"
167
+
168
+ _STATIC_VALUE_FONT = QFont("Arial", 16)
169
+
170
+ def __init__(self, app_model, component, xpos, ypos):
171
+ super().__init__(app_model, component, xpos, ypos)
172
+ self.show_name(False)
173
+
174
+ def paint_component(self, painter):
175
+ if self.component.O.width == 1:
176
+ super().paint_component(painter)
177
+ else:
178
+ self.paint_component_base(painter)
179
+ value_str = f"{self.component.parameter_get('value')}"
180
+ str_w, str_h = self.get_string_metrics(value_str, font=self._STATIC_VALUE_FONT)
181
+ painter.setFont(self._STATIC_VALUE_FONT)
182
+ painter.drawText(
183
+ self.rect().x() + self.rect().width() / 2 - str_w / 2,
184
+ self.rect().y() + str_h,
185
+ value_str,
186
+ )
187
+
188
+
189
+ class ImageObjectLed(ImageObject):
190
+ """The class for a Led image component placed in the GUI"""
191
+
192
+ IMAGE_FILENAME = "images/LED_OFF.png"
193
+ ACTIVE_IMAGE_FILENAME = "images/LED_ON.png"
194
+
195
+ def __init__(self, app_model, component, xpos, ypos):
196
+ super().__init__(app_model, component, xpos, ypos)
197
+ self.show_name(False)
198
+ self.paint_port_names(False)
199
+
200
+
201
+ class ImageObjectIC(ImageObject):
202
+ """The class for a Yosys image component placed in the GUI"""
203
+
204
+ IMAGE_FILENAME = "images/IC.png"
205
+
206
+ def paint_component(self, painter):
207
+ self.paint_component_base(painter)
208
+ self.paint_component_name(painter)
209
+
210
+
211
+ class ImageObjectWithActiveRect(ImageObject):
212
+ """
213
+ A base class for a image component placed in the GUI.
214
+ When active the image will have a green border painted around it.
215
+ """
216
+
217
+ _ACTIVE_RECT_PEN = QPen(Qt.green)
218
+
219
+ def __init__(self, app_model, component, xpos, ypos):
220
+ super().__init__(app_model, component, xpos, ypos)
221
+ self.show_name(False)
222
+ self.paint_port_names(False)
223
+
224
+ def paint_component(self, painter):
225
+ super().paint_component(painter)
226
+ if self.component.active:
227
+ xpos = self.object_pos.x() + self.size.width() / 2 - self._pixmap.width() / 2
228
+ ypos = self.object_pos.y() + self.size.height() / 2 - self._pixmap.height() / 2
229
+ pen = self._ACTIVE_RECT_PEN
230
+ pen.setWidth(4)
231
+ painter.setPen(pen)
232
+ painter.setBrush(Qt.NoBrush)
233
+ painter.drawRoundedRect(xpos, ypos, self._pixmap.width(), self._pixmap.height(), 5, 5)
234
+
235
+
236
+ class ImageObjectClock(ImageObjectWithActiveRect):
237
+ """The class for a Clock image component placed in the GUI"""
238
+
239
+ IMAGE_FILENAME = "images/Clock.png"