digsim-logic-simulator 0.11.0__py3-none-any.whl → 0.13.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.
Potentially problematic release.
This version of digsim-logic-simulator might be problematic. Click here for more details.
- digsim/app/__main__.py +16 -12
- digsim/app/gui/_circuit_area.py +32 -21
- digsim/app/gui/_component_selection.py +2 -0
- digsim/app/gui_objects/__init__.py +1 -1
- digsim/app/gui_objects/_buzzer_object.py +7 -12
- digsim/app/gui_objects/_component_context_menu.py +1 -1
- digsim/app/gui_objects/_component_object.py +10 -9
- digsim/app/gui_objects/_component_port_item.py +0 -2
- digsim/app/gui_objects/_dip_switch_object.py +5 -4
- digsim/app/gui_objects/_gui_note_object.py +10 -11
- digsim/app/gui_objects/_gui_object_factory.py +1 -1
- digsim/app/gui_objects/_image_objects.py +7 -5
- digsim/app/gui_objects/_label_object.py +3 -2
- digsim/app/gui_objects/_logic_analyzer_object.py +18 -9
- digsim/app/gui_objects/_seven_segment_object.py +10 -3
- digsim/app/gui_objects/_shortcut_objects.py +3 -2
- digsim/app/model/_model.py +7 -3
- digsim/app/model/_model_components.py +1 -4
- digsim/app/model/_model_new_wire.py +2 -1
- digsim/app/model/_model_objects.py +1 -5
- digsim/app/model/_model_settings.py +1 -1
- digsim/app/model/_model_shortcuts.py +0 -14
- digsim/app/settings/_component_settings.py +1 -1
- digsim/app/settings/_shortcut_dialog.py +3 -4
- digsim/circuit/_circuit.py +52 -38
- digsim/circuit/components/_button.py +1 -5
- digsim/circuit/components/_yosys_component.py +1 -1
- digsim/circuit/components/atoms/__init__.py +1 -0
- digsim/circuit/components/atoms/_component.py +14 -4
- digsim/circuit/components/atoms/_port.py +45 -29
- digsim/storage_model/_app.py +7 -2
- digsim/storage_model/_circuit.py +15 -7
- digsim/synth/__main__.py +2 -2
- digsim/synth/_synthesis.py +26 -1
- digsim/utils/_yosys_netlist.py +9 -3
- {digsim_logic_simulator-0.11.0.dist-info → digsim_logic_simulator-0.13.0.dist-info}/METADATA +1 -1
- {digsim_logic_simulator-0.11.0.dist-info → digsim_logic_simulator-0.13.0.dist-info}/RECORD +40 -40
- {digsim_logic_simulator-0.11.0.dist-info → digsim_logic_simulator-0.13.0.dist-info}/WHEEL +0 -0
- {digsim_logic_simulator-0.11.0.dist-info → digsim_logic_simulator-0.13.0.dist-info}/licenses/LICENSE.md +0 -0
- {digsim_logic_simulator-0.11.0.dist-info → digsim_logic_simulator-0.13.0.dist-info}/top_level.txt +0 -0
|
@@ -38,20 +38,6 @@ class ModelShortcuts:
|
|
|
38
38
|
"""Get shortcut"""
|
|
39
39
|
return self._shortcut_component.get(key)
|
|
40
40
|
|
|
41
|
-
def _qtkey_to_key(self, qt_key):
|
|
42
|
-
return {
|
|
43
|
-
Qt.Key_0: "0",
|
|
44
|
-
Qt.Key_1: "1",
|
|
45
|
-
Qt.Key_2: "2",
|
|
46
|
-
Qt.Key_3: "3",
|
|
47
|
-
Qt.Key_4: "4",
|
|
48
|
-
Qt.Key_5: "5",
|
|
49
|
-
Qt.Key_6: "6",
|
|
50
|
-
Qt.Key_7: "7",
|
|
51
|
-
Qt.Key_8: "8",
|
|
52
|
-
Qt.Key_9: "9",
|
|
53
|
-
}.get(qt_key)
|
|
54
|
-
|
|
55
41
|
def press(self, qtkey):
|
|
56
42
|
"""Handle shortcut keypress"""
|
|
57
43
|
key = self.QT_KEY_TO_KEY.get(qtkey)
|
|
@@ -271,7 +271,7 @@ class ComponentSettingsCheckBoxWidthBool(ComponentSettingsBase):
|
|
|
271
271
|
checkbox = QCheckBox(f"bit{checkbox_id}")
|
|
272
272
|
checkbox.setChecked(True)
|
|
273
273
|
checkbox.stateChanged.connect(self._update)
|
|
274
|
-
self.layout().addWidget(checkbox, checkbox_id % 8, checkbox_id
|
|
274
|
+
self.layout().addWidget(checkbox, checkbox_id % 8, checkbox_id // 8, 1, 1)
|
|
275
275
|
self._bit_checkboxes.append(checkbox)
|
|
276
276
|
disable_all = QPushButton("Disable All")
|
|
277
277
|
disable_all.clicked.connect(self._disable_all)
|
|
@@ -20,12 +20,11 @@ class ShortcutDialog(QDialog):
|
|
|
20
20
|
self.layout().addWidget(QLabel("Select shortcut key for component"))
|
|
21
21
|
self._key_selector = QComboBox(parent)
|
|
22
22
|
for key in "1234567890":
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
shortcut_component = app_model.shortcuts.get_component(shortcut_key)
|
|
23
|
+
shortcut_string = key
|
|
24
|
+
shortcut_component = app_model.shortcuts.get_component(key)
|
|
26
25
|
if shortcut_component is not None:
|
|
27
26
|
shortcut_string += f" - {shortcut_component.name()}"
|
|
28
|
-
self._key_selector.addItem(shortcut_string, userData=
|
|
27
|
+
self._key_selector.addItem(shortcut_string, userData=key)
|
|
29
28
|
self.layout().addWidget(self._key_selector)
|
|
30
29
|
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
31
30
|
self.buttonBox.accepted.connect(self.accept)
|
digsim/circuit/_circuit.py
CHANGED
|
@@ -7,13 +7,14 @@ Module that handles the circuit simulation of components
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import heapq
|
|
11
|
+
import pathlib
|
|
11
12
|
from typing import Tuple
|
|
12
13
|
|
|
13
14
|
from digsim.storage_model import CircuitDataClass, CircuitFileDataClass
|
|
14
15
|
|
|
15
16
|
from ._waves_writer import WavesWriter
|
|
16
|
-
from .components.atoms import Component, DigsimException, PortOutDelta
|
|
17
|
+
from .components.atoms import VALUE_TYPE, Component, DigsimException, PortOutDelta
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class CircuitError(DigsimException):
|
|
@@ -26,10 +27,10 @@ class CircuitEvent:
|
|
|
26
27
|
delta events in the simulation.
|
|
27
28
|
"""
|
|
28
29
|
|
|
29
|
-
def __init__(self, time_ns: int, port: PortOutDelta, value:
|
|
30
|
+
def __init__(self, time_ns: int, port: PortOutDelta, value: VALUE_TYPE):
|
|
30
31
|
self._time_ns: int = time_ns
|
|
31
32
|
self._port: PortOutDelta = port
|
|
32
|
-
self._value:
|
|
33
|
+
self._value: VALUE_TYPE = value
|
|
33
34
|
|
|
34
35
|
@property
|
|
35
36
|
def time_ns(self) -> int:
|
|
@@ -42,7 +43,7 @@ class CircuitEvent:
|
|
|
42
43
|
return self._port
|
|
43
44
|
|
|
44
45
|
@property
|
|
45
|
-
def value(self) ->
|
|
46
|
+
def value(self) -> VALUE_TYPE:
|
|
46
47
|
"""Get the delta cycle value of this event"""
|
|
47
48
|
return self._value
|
|
48
49
|
|
|
@@ -50,7 +51,7 @@ class CircuitEvent:
|
|
|
50
51
|
"""Return True if the in the event is the same as"""
|
|
51
52
|
return port == self._port
|
|
52
53
|
|
|
53
|
-
def update(self, time_ns: int, value:
|
|
54
|
+
def update(self, time_ns: int, value: VALUE_TYPE):
|
|
54
55
|
"""Update the event with a new time (ns) and a new value"""
|
|
55
56
|
self._time_ns = time_ns
|
|
56
57
|
self._value = value
|
|
@@ -58,6 +59,14 @@ class CircuitEvent:
|
|
|
58
59
|
def __lt__(self, other) -> bool:
|
|
59
60
|
return other.time_ns > self.time_ns
|
|
60
61
|
|
|
62
|
+
def __eq__(self, other) -> bool:
|
|
63
|
+
return (
|
|
64
|
+
isinstance(other, CircuitEvent)
|
|
65
|
+
and self._port == other._port
|
|
66
|
+
and self._time_ns == other._time_ns
|
|
67
|
+
and self._value == other._value
|
|
68
|
+
)
|
|
69
|
+
|
|
61
70
|
|
|
62
71
|
class Circuit:
|
|
63
72
|
"""Class thay handles the circuit simulation"""
|
|
@@ -65,6 +74,7 @@ class Circuit:
|
|
|
65
74
|
def __init__(self, name: str | None = None, vcd: str | None = None):
|
|
66
75
|
self._components: dict[str, Component] = {}
|
|
67
76
|
self._circuit_events: list[CircuitEvent] = []
|
|
77
|
+
self._events_by_port: dict[PortOutDelta, CircuitEvent] = {}
|
|
68
78
|
self._name: str | None = name
|
|
69
79
|
self._time_ns: int = 0
|
|
70
80
|
self._folder: str | None = None
|
|
@@ -86,10 +96,7 @@ class Circuit:
|
|
|
86
96
|
@property
|
|
87
97
|
def components(self) -> list[Component]:
|
|
88
98
|
"""Get the components in this circuit"""
|
|
89
|
-
|
|
90
|
-
for _, comp in self._components.items():
|
|
91
|
-
comp_array.append(comp)
|
|
92
|
-
return comp_array
|
|
99
|
+
return list(self._components.values())
|
|
93
100
|
|
|
94
101
|
def load_path(self, path) -> str:
|
|
95
102
|
"""Get the load path relative to the circuit path"""
|
|
@@ -100,7 +107,9 @@ class Circuit:
|
|
|
100
107
|
def store_path(self, path) -> str:
|
|
101
108
|
"""Get the store path relative to the circuit path"""
|
|
102
109
|
if self._folder is not None:
|
|
103
|
-
return
|
|
110
|
+
return str(
|
|
111
|
+
pathlib.Path(path).resolve().absolute().relative_to(pathlib.Path(self._folder))
|
|
112
|
+
)
|
|
104
113
|
return path
|
|
105
114
|
|
|
106
115
|
def delete_component(self, component: Component):
|
|
@@ -110,16 +119,13 @@ class Circuit:
|
|
|
110
119
|
|
|
111
120
|
def get_toplevel_components(self) -> list[Component]:
|
|
112
121
|
"""Get toplevel components in the circuit"""
|
|
113
|
-
|
|
114
|
-
for _, comp in self._components.items():
|
|
115
|
-
if comp.is_toplevel():
|
|
116
|
-
toplevel_components.append(comp)
|
|
117
|
-
return toplevel_components
|
|
122
|
+
return [comp for comp in self._components.values() if comp.is_toplevel()]
|
|
118
123
|
|
|
119
124
|
def init(self):
|
|
120
125
|
"""Initialize circuit and components (and ports)"""
|
|
121
126
|
self._time_ns = 0
|
|
122
127
|
self._circuit_events = []
|
|
128
|
+
self._events_by_port = {}
|
|
123
129
|
if self._vcd is not None:
|
|
124
130
|
self._vcd_init()
|
|
125
131
|
for _, comp in self._components.items():
|
|
@@ -175,22 +181,32 @@ class Circuit:
|
|
|
175
181
|
Process one simulation event
|
|
176
182
|
Return False if ther are now events of if the stop_time has passed
|
|
177
183
|
"""
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
184
|
+
while self._circuit_events:
|
|
185
|
+
event = heapq.heappop(self._circuit_events)
|
|
186
|
+
|
|
187
|
+
# Check if this is the latest event for this port
|
|
188
|
+
if event != self._events_by_port.get(event.port):
|
|
189
|
+
# This is a stale event, ignore it
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
if stop_time_ns is not None and event.time_ns > stop_time_ns:
|
|
193
|
+
# Put the event back if it's after the stop time
|
|
194
|
+
heapq.heappush(self._circuit_events, event)
|
|
195
|
+
return False, False
|
|
196
|
+
|
|
197
|
+
del self._events_by_port[event.port]
|
|
198
|
+
# print(
|
|
199
|
+
# f"Execute event {event.port.path()}.{event.port.name()}"
|
|
200
|
+
# f" {event.time_ns} {event.value}"
|
|
201
|
+
# )
|
|
202
|
+
self._time_ns = event.time_ns
|
|
203
|
+
event.port.delta_cycle(event.value)
|
|
204
|
+
toplevel = event.port.parent().is_toplevel()
|
|
205
|
+
if self._vcd is not None:
|
|
206
|
+
self._vcd.write(event.port, self._time_ns)
|
|
207
|
+
return True, toplevel
|
|
208
|
+
|
|
209
|
+
return False, False
|
|
194
210
|
|
|
195
211
|
def _is_toplevel_event(self) -> bool:
|
|
196
212
|
if len(self._circuit_events) == 0:
|
|
@@ -231,15 +247,13 @@ class Circuit:
|
|
|
231
247
|
if stop_time_ns >= self._time_ns:
|
|
232
248
|
self.run(ns=stop_time_ns - self._time_ns)
|
|
233
249
|
|
|
234
|
-
def add_event(self, port: PortOutDelta, value:
|
|
250
|
+
def add_event(self, port: PortOutDelta, value: VALUE_TYPE, propagation_delay_ns: int):
|
|
235
251
|
"""Add delta cycle event, this will also write values to .vcd file"""
|
|
236
252
|
event_time_ns = self._time_ns + propagation_delay_ns
|
|
237
253
|
# print(f"Add event {port.parent().name()}:{port.name()} => {value}")
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
return
|
|
242
|
-
self._circuit_events.append(CircuitEvent(event_time_ns, port, value))
|
|
254
|
+
event = CircuitEvent(event_time_ns, port, value)
|
|
255
|
+
self._events_by_port[port] = event
|
|
256
|
+
heapq.heappush(self._circuit_events, event)
|
|
243
257
|
|
|
244
258
|
def add_component(self, component: Component):
|
|
245
259
|
"""Add component to circuit"""
|
|
@@ -3,21 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
"""A PushButton component"""
|
|
5
5
|
|
|
6
|
-
import logging
|
|
7
|
-
|
|
8
6
|
from .atoms import CallbackComponent, PortOutImmediate
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
class PushButton(CallbackComponent):
|
|
12
10
|
"""PushButton component class"""
|
|
13
11
|
|
|
14
|
-
def __init__(self, circuit, name=None
|
|
12
|
+
def __init__(self, circuit, name=None):
|
|
15
13
|
super().__init__(circuit, name)
|
|
16
14
|
portout = PortOutImmediate(self, "O")
|
|
17
15
|
self.add_port(portout)
|
|
18
16
|
portout.update_parent(True)
|
|
19
|
-
if inverted:
|
|
20
|
-
logging.warning("Setting 'inverted' has been removed")
|
|
21
17
|
|
|
22
18
|
def default_state(self):
|
|
23
19
|
self.release()
|
|
@@ -26,7 +26,7 @@ class YosysComponent(MultiComponent):
|
|
|
26
26
|
def __init__(self, circuit, path=None, name=None, nets=True):
|
|
27
27
|
super().__init__(circuit, name)
|
|
28
28
|
self._circuit = circuit
|
|
29
|
-
self._path = path
|
|
29
|
+
self._path = str(path)
|
|
30
30
|
self._gates_comp = None
|
|
31
31
|
self._net_comp = None
|
|
32
32
|
self._netlist_module = None
|
|
@@ -63,10 +63,20 @@ class Component(abc.ABC):
|
|
|
63
63
|
self._ports = []
|
|
64
64
|
|
|
65
65
|
def path(self) -> str:
|
|
66
|
-
"""Get component path"""
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
"""Get component path (iterative)"""
|
|
67
|
+
path_parts = []
|
|
68
|
+
current_component = self
|
|
69
|
+
while current_component is not None:
|
|
70
|
+
name = current_component.name()
|
|
71
|
+
if not isinstance(name, str):
|
|
72
|
+
raise TypeError(
|
|
73
|
+
f"Component name is not a string: "
|
|
74
|
+
f"{name} ({type(name)}), "
|
|
75
|
+
f"component: {current_component.__class__.__name__}"
|
|
76
|
+
)
|
|
77
|
+
path_parts.append(name)
|
|
78
|
+
current_component = current_component._parent
|
|
79
|
+
return ".".join(reversed(path_parts))
|
|
70
80
|
|
|
71
81
|
@property
|
|
72
82
|
def ports(self):
|
|
@@ -6,10 +6,14 @@
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
8
|
import abc
|
|
9
|
+
from typing import Literal, Optional, Union
|
|
9
10
|
|
|
10
11
|
from ._digsim_exception import DigsimException
|
|
11
12
|
|
|
12
13
|
|
|
14
|
+
VALUE_TYPE = Union[int, Literal["X"]]
|
|
15
|
+
|
|
16
|
+
|
|
13
17
|
class PortConnectionError(DigsimException):
|
|
14
18
|
"""Exception for illegal connections"""
|
|
15
19
|
|
|
@@ -23,8 +27,8 @@ class Port(abc.ABC):
|
|
|
23
27
|
self._width: int = width # The bit-width of this port
|
|
24
28
|
self._output: bool = output # Is this port an output port
|
|
25
29
|
self._wired_ports: list[Port] = [] # The ports that this port drives
|
|
26
|
-
self._value:
|
|
27
|
-
self._edge_detect_value:
|
|
30
|
+
self._value: VALUE_TYPE = "X" # The value of this port
|
|
31
|
+
self._edge_detect_value: VALUE_TYPE = "X" # Last edge detect value
|
|
28
32
|
self.init() # Initialize the port
|
|
29
33
|
|
|
30
34
|
def init(self):
|
|
@@ -39,12 +43,12 @@ class Port(abc.ABC):
|
|
|
39
43
|
return self._wired_ports
|
|
40
44
|
|
|
41
45
|
@property
|
|
42
|
-
def value(self) ->
|
|
46
|
+
def value(self) -> VALUE_TYPE:
|
|
43
47
|
"""Get the value of the port, can be "X" """
|
|
44
48
|
return self._value
|
|
45
49
|
|
|
46
50
|
@value.setter
|
|
47
|
-
def value(self, value:
|
|
51
|
+
def value(self, value: VALUE_TYPE):
|
|
48
52
|
"""Set the value of the port"""
|
|
49
53
|
self.set_value(value)
|
|
50
54
|
|
|
@@ -60,7 +64,7 @@ class Port(abc.ABC):
|
|
|
60
64
|
driver = self.get_driver()
|
|
61
65
|
if driver is not None:
|
|
62
66
|
driver.disconnect(self)
|
|
63
|
-
for port in self._wired_ports:
|
|
67
|
+
for port in self._wired_ports[:]:
|
|
64
68
|
self.disconnect(port)
|
|
65
69
|
self._width = width
|
|
66
70
|
|
|
@@ -98,7 +102,7 @@ class Port(abc.ABC):
|
|
|
98
102
|
"""Get parent component"""
|
|
99
103
|
return self._parent
|
|
100
104
|
|
|
101
|
-
def update_wires(self, value:
|
|
105
|
+
def update_wires(self, value: VALUE_TYPE):
|
|
102
106
|
"""Update connected wires (and self._value) with value"""
|
|
103
107
|
if self._value == value:
|
|
104
108
|
return
|
|
@@ -106,11 +110,21 @@ class Port(abc.ABC):
|
|
|
106
110
|
for port in self._wired_ports:
|
|
107
111
|
port.value = self._value
|
|
108
112
|
|
|
109
|
-
def get_wired_ports_recursive(self) -> list[Port]:
|
|
110
|
-
"""Get all connected ports (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
def get_wired_ports_recursive(self, processed_ports: Optional[set] = None) -> list[Port]:
|
|
114
|
+
"""Get all connected ports (iterative), avoiding duplicates."""
|
|
115
|
+
if processed_ports is None:
|
|
116
|
+
processed_ports = set()
|
|
117
|
+
|
|
118
|
+
all_wired_ports = []
|
|
119
|
+
ports_to_process = [self]
|
|
120
|
+
|
|
121
|
+
while ports_to_process:
|
|
122
|
+
port = ports_to_process.pop()
|
|
123
|
+
if port in processed_ports:
|
|
124
|
+
continue
|
|
125
|
+
processed_ports.add(port)
|
|
126
|
+
all_wired_ports.append(port)
|
|
127
|
+
ports_to_process.extend(port.wired_ports)
|
|
114
128
|
return all_wired_ports
|
|
115
129
|
|
|
116
130
|
def is_output(self) -> bool:
|
|
@@ -144,7 +158,7 @@ class Port(abc.ABC):
|
|
|
144
158
|
return falling_edge
|
|
145
159
|
|
|
146
160
|
@abc.abstractmethod
|
|
147
|
-
def set_value(self, value:
|
|
161
|
+
def set_value(self, value: VALUE_TYPE):
|
|
148
162
|
"""Set value on port"""
|
|
149
163
|
|
|
150
164
|
@abc.abstractmethod
|
|
@@ -195,7 +209,7 @@ class PortWire(Port):
|
|
|
195
209
|
super().__init__(parent, name, width, output)
|
|
196
210
|
self._port_driver: Port | None = None # The port that drives this port
|
|
197
211
|
|
|
198
|
-
def set_value(self, value:
|
|
212
|
+
def set_value(self, value: VALUE_TYPE):
|
|
199
213
|
if value != self.value:
|
|
200
214
|
self.update_wires(value)
|
|
201
215
|
|
|
@@ -221,7 +235,7 @@ class PortIn(PortWire):
|
|
|
221
235
|
def __init__(self, parent, name: str, width: int = 1):
|
|
222
236
|
super().__init__(parent, name, width, output=False)
|
|
223
237
|
|
|
224
|
-
def set_value(self, value:
|
|
238
|
+
def set_value(self, value: VALUE_TYPE):
|
|
225
239
|
super().set_value(value)
|
|
226
240
|
self.parent().update()
|
|
227
241
|
|
|
@@ -246,16 +260,16 @@ class PortOutDelta(Port):
|
|
|
246
260
|
"""Set port propagation delay"""
|
|
247
261
|
self._delay_ns = delay_ns
|
|
248
262
|
|
|
249
|
-
def set_value(self, value:
|
|
263
|
+
def set_value(self, value: VALUE_TYPE):
|
|
250
264
|
self.parent().add_event(self, value, self._delay_ns)
|
|
251
265
|
|
|
252
|
-
def update_port(self, value:
|
|
266
|
+
def update_port(self, value: VALUE_TYPE):
|
|
253
267
|
"""Update the port output and the connected wires"""
|
|
254
268
|
self.update_wires(value)
|
|
255
269
|
if self._update_parent:
|
|
256
270
|
self.parent().update()
|
|
257
271
|
|
|
258
|
-
def delta_cycle(self, value:
|
|
272
|
+
def delta_cycle(self, value: VALUE_TYPE):
|
|
259
273
|
"""Handle the delta cycle event from the circuit"""
|
|
260
274
|
self.update_port(value)
|
|
261
275
|
|
|
@@ -282,11 +296,11 @@ class PortOutImmediate(PortOutDelta):
|
|
|
282
296
|
def __init__(self, parent, name: str, width: int = 1):
|
|
283
297
|
super().__init__(parent, name, width)
|
|
284
298
|
|
|
285
|
-
def set_value(self, value:
|
|
299
|
+
def set_value(self, value: VALUE_TYPE):
|
|
286
300
|
self.parent().add_event(self, value, 0)
|
|
287
301
|
super().update_port(value)
|
|
288
302
|
|
|
289
|
-
def delta_cycle(self, value:
|
|
303
|
+
def delta_cycle(self, value: VALUE_TYPE):
|
|
290
304
|
"""
|
|
291
305
|
Do nothing here, the event is just used to updates waves in Circuit class
|
|
292
306
|
"""
|
|
@@ -303,7 +317,7 @@ class PortWireBit(PortWire):
|
|
|
303
317
|
super().__init__(parent, name, 1, output)
|
|
304
318
|
self._parent_port = parent_port
|
|
305
319
|
|
|
306
|
-
def set_value(self, value:
|
|
320
|
+
def set_value(self, value: VALUE_TYPE):
|
|
307
321
|
super().set_value(value)
|
|
308
322
|
self._parent_port.update_value_from_bits()
|
|
309
323
|
|
|
@@ -333,17 +347,20 @@ class PortMultiBitWire(Port):
|
|
|
333
347
|
for bit in self._bits:
|
|
334
348
|
bit.init()
|
|
335
349
|
|
|
336
|
-
def set_value(self, value:
|
|
337
|
-
if
|
|
350
|
+
def set_value(self, value: VALUE_TYPE):
|
|
351
|
+
if isinstance(value, str):
|
|
338
352
|
return
|
|
339
353
|
for bit_id, bit in enumerate(self._bits):
|
|
340
354
|
bit_val = (value >> bit_id) & 1
|
|
341
355
|
bit.value = bit_val
|
|
342
356
|
|
|
343
|
-
def get_wired_ports_recursive(self) -> list[Port]:
|
|
344
|
-
|
|
357
|
+
def get_wired_ports_recursive(self, processed_ports: Optional[set] = None) -> list[Port]:
|
|
358
|
+
if processed_ports is None:
|
|
359
|
+
processed_ports = set()
|
|
360
|
+
|
|
361
|
+
all_wired_ports = super().get_wired_ports_recursive(processed_ports)
|
|
345
362
|
for bit in self._bits:
|
|
346
|
-
all_wired_ports.extend(bit.get_wired_ports_recursive())
|
|
363
|
+
all_wired_ports.extend(bit.get_wired_ports_recursive(processed_ports))
|
|
347
364
|
return all_wired_ports
|
|
348
365
|
|
|
349
366
|
def set_driver(self, port: Port | None):
|
|
@@ -364,18 +381,17 @@ class PortMultiBitWire(Port):
|
|
|
364
381
|
|
|
365
382
|
def update_value_from_bits(self):
|
|
366
383
|
"""Update the port with the value of the bits"""
|
|
367
|
-
|
|
384
|
+
value = 0
|
|
385
|
+
for bit_id, bit in enumerate(self._bits):
|
|
368
386
|
if bit.value == "X":
|
|
369
387
|
self.update_wires("X")
|
|
370
388
|
return
|
|
371
|
-
value = 0
|
|
372
|
-
for bit_id, bit in enumerate(self._bits):
|
|
373
389
|
value = value | (bit.value << bit_id)
|
|
374
390
|
self.update_wires(value)
|
|
375
391
|
# Send event just to update waves
|
|
376
392
|
self.parent().add_event(self, value, 0)
|
|
377
393
|
|
|
378
|
-
def delta_cycle(self, value:
|
|
394
|
+
def delta_cycle(self, value: VALUE_TYPE):
|
|
379
395
|
"""
|
|
380
396
|
Do nothing here, the event passed in 'update_value_from_bits'
|
|
381
397
|
is just used to updates waves in Circuit class
|
digsim/storage_model/_app.py
CHANGED
|
@@ -33,8 +33,13 @@ class AppFileDataClass:
|
|
|
33
33
|
|
|
34
34
|
@staticmethod
|
|
35
35
|
def load(filename):
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
38
43
|
return app_filedata_class
|
|
39
44
|
|
|
40
45
|
def save(self, filename):
|
digsim/storage_model/_circuit.py
CHANGED
|
@@ -21,9 +21,11 @@ class WireDataClass:
|
|
|
21
21
|
dst: str
|
|
22
22
|
|
|
23
23
|
def connect(self, circuit):
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
src_comp
|
|
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)
|
|
27
29
|
|
|
28
30
|
@classmethod
|
|
29
31
|
def list_from_port(cls, src_port):
|
|
@@ -92,10 +94,11 @@ class CircuitDataClass:
|
|
|
92
94
|
@staticmethod
|
|
93
95
|
def from_circuit(circuit):
|
|
94
96
|
dc = CircuitDataClass(name=circuit.name)
|
|
95
|
-
|
|
97
|
+
toplevel_components = circuit.get_toplevel_components()
|
|
98
|
+
for comp in toplevel_components:
|
|
96
99
|
dc.components.append(ComponentDataClass.from_component(comp))
|
|
97
100
|
|
|
98
|
-
for comp in
|
|
101
|
+
for comp in toplevel_components:
|
|
99
102
|
for port in comp.ports:
|
|
100
103
|
dc.wires.extend(WireDataClass.list_from_port(port))
|
|
101
104
|
|
|
@@ -108,8 +111,13 @@ class CircuitFileDataClass:
|
|
|
108
111
|
|
|
109
112
|
@staticmethod
|
|
110
113
|
def load(filename):
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
113
121
|
return dc
|
|
114
122
|
|
|
115
123
|
def save(self, filename):
|
digsim/synth/__main__.py
CHANGED
|
@@ -45,7 +45,7 @@ if __name__ == "__main__":
|
|
|
45
45
|
subparser = parser.add_subparsers(required=True)
|
|
46
46
|
synth_parser = subparser.add_parser("synth")
|
|
47
47
|
synth_parser.add_argument(
|
|
48
|
-
"--input-files", "-i", type=str, nargs="
|
|
48
|
+
"--input-files", "-i", type=str, nargs="+", required=True, help="The verilog input files"
|
|
49
49
|
)
|
|
50
50
|
synth_parser.add_argument(
|
|
51
51
|
"--output-file", "-o", type=str, required=True, help="The json output file"
|
|
@@ -59,7 +59,7 @@ if __name__ == "__main__":
|
|
|
59
59
|
synth_parser.set_defaults(func=_synth_modules)
|
|
60
60
|
list_parser = subparser.add_parser("list")
|
|
61
61
|
list_parser.add_argument(
|
|
62
|
-
"--input-files", "-i", type=str, nargs="
|
|
62
|
+
"--input-files", "-i", type=str, nargs="+", required=True, help="The verilog input files"
|
|
63
63
|
)
|
|
64
64
|
list_parser.set_defaults(func=_list_modules)
|
|
65
65
|
arguments = parser.parse_args()
|
digsim/synth/_synthesis.py
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
"""Helper module for yosys synthesis"""
|
|
5
5
|
|
|
6
6
|
import json
|
|
7
|
+
import pathlib
|
|
7
8
|
import shutil
|
|
9
|
+
import site
|
|
8
10
|
import sys
|
|
9
11
|
|
|
10
12
|
import pexpect
|
|
@@ -43,10 +45,30 @@ class Synthesis:
|
|
|
43
45
|
out_lines.append(line)
|
|
44
46
|
return out_lines
|
|
45
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
|
+
|
|
46
62
|
@classmethod
|
|
47
63
|
def _pexpect_spawn_yosys(cls):
|
|
64
|
+
# Find linux binary
|
|
48
65
|
yosys_exe = shutil.which("yosys") or shutil.which("yowasp-yosys")
|
|
49
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
|
+
|
|
50
72
|
if yosys_exe is None:
|
|
51
73
|
raise SynthesisException("Yosys executable not found")
|
|
52
74
|
|
|
@@ -115,7 +137,10 @@ class Synthesis:
|
|
|
115
137
|
def synth_to_dict(self, silent=False):
|
|
116
138
|
"""Execute yosys with generated synthesis script and return python dict"""
|
|
117
139
|
yosys_json = self.synth_to_json(silent)
|
|
118
|
-
|
|
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
|
|
119
144
|
return netlist_dict
|
|
120
145
|
|
|
121
146
|
def synth_to_json_file(self, filename, silent=False):
|