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.
- digsim/__init__.py +6 -0
- digsim/app/__main__.py +12 -0
- digsim/app/cli.py +68 -0
- digsim/app/gui/__init__.py +6 -0
- digsim/app/gui/_circuit_area.py +468 -0
- digsim/app/gui/_component_selection.py +154 -0
- digsim/app/gui/_main_window.py +163 -0
- digsim/app/gui/_top_bar.py +339 -0
- digsim/app/gui/_utils.py +26 -0
- digsim/app/gui/_warning_dialog.py +46 -0
- digsim/app/gui_objects/__init__.py +7 -0
- digsim/app/gui_objects/_bus_bit_object.py +94 -0
- digsim/app/gui_objects/_buzzer_object.py +97 -0
- digsim/app/gui_objects/_component_context_menu.py +79 -0
- digsim/app/gui_objects/_component_object.py +374 -0
- digsim/app/gui_objects/_component_port_item.py +63 -0
- digsim/app/gui_objects/_dip_switch_object.py +104 -0
- digsim/app/gui_objects/_gui_note_object.py +80 -0
- digsim/app/gui_objects/_gui_object_factory.py +80 -0
- digsim/app/gui_objects/_hexdigit_object.py +53 -0
- digsim/app/gui_objects/_image_objects.py +239 -0
- digsim/app/gui_objects/_label_object.py +97 -0
- digsim/app/gui_objects/_logic_analyzer_object.py +86 -0
- digsim/app/gui_objects/_seven_segment_object.py +131 -0
- digsim/app/gui_objects/_shortcut_objects.py +82 -0
- digsim/app/gui_objects/_yosys_object.py +32 -0
- digsim/app/gui_objects/images/AND.png +0 -0
- digsim/app/gui_objects/images/Analyzer.png +0 -0
- digsim/app/gui_objects/images/BUF.png +0 -0
- digsim/app/gui_objects/images/Buzzer.png +0 -0
- digsim/app/gui_objects/images/Clock.png +0 -0
- digsim/app/gui_objects/images/DFF.png +0 -0
- digsim/app/gui_objects/images/DIP_SWITCH.png +0 -0
- digsim/app/gui_objects/images/FlipFlop.png +0 -0
- digsim/app/gui_objects/images/IC.png +0 -0
- digsim/app/gui_objects/images/LED_OFF.png +0 -0
- digsim/app/gui_objects/images/LED_ON.png +0 -0
- digsim/app/gui_objects/images/MUX.png +0 -0
- digsim/app/gui_objects/images/NAND.png +0 -0
- digsim/app/gui_objects/images/NOR.png +0 -0
- digsim/app/gui_objects/images/NOT.png +0 -0
- digsim/app/gui_objects/images/ONE.png +0 -0
- digsim/app/gui_objects/images/OR.png +0 -0
- digsim/app/gui_objects/images/PB.png +0 -0
- digsim/app/gui_objects/images/Switch_OFF.png +0 -0
- digsim/app/gui_objects/images/Switch_ON.png +0 -0
- digsim/app/gui_objects/images/XNOR.png +0 -0
- digsim/app/gui_objects/images/XOR.png +0 -0
- digsim/app/gui_objects/images/YOSYS.png +0 -0
- digsim/app/gui_objects/images/ZERO.png +0 -0
- digsim/app/images/app_icon.png +0 -0
- digsim/app/model/__init__.py +6 -0
- digsim/app/model/_model.py +210 -0
- digsim/app/model/_model_components.py +162 -0
- digsim/app/model/_model_new_wire.py +57 -0
- digsim/app/model/_model_objects.py +155 -0
- digsim/app/model/_model_settings.py +35 -0
- digsim/app/model/_model_shortcuts.py +72 -0
- digsim/app/settings/__init__.py +8 -0
- digsim/app/settings/_component_settings.py +415 -0
- digsim/app/settings/_gui_settings.py +71 -0
- digsim/app/settings/_shortcut_dialog.py +39 -0
- digsim/circuit/__init__.py +7 -0
- digsim/circuit/_circuit.py +329 -0
- digsim/circuit/_waves_writer.py +61 -0
- digsim/circuit/components/__init__.py +26 -0
- digsim/circuit/components/_bus_bits.py +68 -0
- digsim/circuit/components/_button.py +44 -0
- digsim/circuit/components/_buzzer.py +45 -0
- digsim/circuit/components/_clock.py +54 -0
- digsim/circuit/components/_dip_switch.py +73 -0
- digsim/circuit/components/_flip_flops.py +99 -0
- digsim/circuit/components/_gates.py +246 -0
- digsim/circuit/components/_hexdigit.py +82 -0
- digsim/circuit/components/_ic.py +36 -0
- digsim/circuit/components/_label_wire.py +167 -0
- digsim/circuit/components/_led.py +18 -0
- digsim/circuit/components/_logic_analyzer.py +60 -0
- digsim/circuit/components/_mem64kbyte.py +42 -0
- digsim/circuit/components/_memstdout.py +37 -0
- digsim/circuit/components/_note.py +25 -0
- digsim/circuit/components/_on_off_switch.py +54 -0
- digsim/circuit/components/_seven_segment.py +28 -0
- digsim/circuit/components/_static_level.py +28 -0
- digsim/circuit/components/_static_value.py +44 -0
- digsim/circuit/components/_yosys_atoms.py +1353 -0
- digsim/circuit/components/_yosys_component.py +232 -0
- digsim/circuit/components/atoms/__init__.py +23 -0
- digsim/circuit/components/atoms/_component.py +280 -0
- digsim/circuit/components/atoms/_digsim_exception.py +8 -0
- digsim/circuit/components/atoms/_port.py +398 -0
- digsim/circuit/components/ic/74162.json +1331 -0
- digsim/circuit/components/ic/7448.json +834 -0
- digsim/storage_model/__init__.py +7 -0
- digsim/storage_model/_app.py +58 -0
- digsim/storage_model/_circuit.py +126 -0
- digsim/synth/__init__.py +6 -0
- digsim/synth/__main__.py +67 -0
- digsim/synth/_synthesis.py +156 -0
- digsim/utils/__init__.py +6 -0
- digsim/utils/_yosys_netlist.py +134 -0
- digsim_logic_simulator-0.22.0.dist-info/METADATA +140 -0
- digsim_logic_simulator-0.22.0.dist-info/RECORD +107 -0
- digsim_logic_simulator-0.22.0.dist-info/WHEEL +5 -0
- digsim_logic_simulator-0.22.0.dist-info/entry_points.txt +2 -0
- digsim_logic_simulator-0.22.0.dist-info/licenses/LICENSE.md +32 -0
- 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"
|