digsim-logic-simulator 0.7.0__py3-none-any.whl → 0.8.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.

@@ -1,4 +1,4 @@
1
- # Copyright (c) Fredrik Andersson, 2023-2024
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
2
  # All rights reserved
3
3
 
4
4
  """The circuit area and component widget"""
@@ -275,7 +275,7 @@ class _CircuitAreaScene(QGraphicsScene):
275
275
  component_objects = self._app_model.objects.components.get_object_list()
276
276
  for src_comp_item in component_objects:
277
277
  for src_port in src_comp_item.component.outports():
278
- for dst_port in src_port.get_wires():
278
+ for dst_port in src_port.wired_ports:
279
279
  dst_comp_item = self._app_model.objects.components.get_object(
280
280
  dst_port.parent()
281
281
  )
@@ -3,6 +3,8 @@
3
3
 
4
4
  """A component with an image as symbol the GUI"""
5
5
 
6
+ from __future__ import annotations
7
+
6
8
  from pathlib import Path
7
9
 
8
10
  from PySide6.QtCore import QPoint, Qt
@@ -14,8 +16,8 @@ from ._component_object import ComponentObject
14
16
  class ImageObject(ComponentObject):
15
17
  """The class for a image component placed in the GUI"""
16
18
 
17
- IMAGE_FILENAME = None
18
- ACTIVE_IMAGE_FILENAME = None
19
+ IMAGE_FILENAME: str | None = None
20
+ ACTIVE_IMAGE_FILENAME: str | None = None
19
21
  _pixmap = None
20
22
  _pixmap_active = None
21
23
 
@@ -1,16 +1,19 @@
1
- # Copyright (c) Fredrik Andersson, 2023-2024
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
2
  # All rights reserved
3
3
 
4
4
  """
5
5
  Module that handles the circuit simulation of components
6
6
  """
7
7
 
8
+ from __future__ import annotations
9
+
8
10
  import os
11
+ from typing import Tuple
9
12
 
10
13
  from digsim.storage_model import CircuitDataClass, CircuitFileDataClass
11
14
 
12
15
  from ._waves_writer import WavesWriter
13
- from .components.atoms import DigsimException
16
+ from .components.atoms import Component, DigsimException, PortOutDelta
14
17
 
15
18
 
16
19
  class CircuitError(DigsimException):
@@ -23,90 +26,89 @@ class CircuitEvent:
23
26
  delta events in the simulation.
24
27
  """
25
28
 
26
- def __init__(self, time_ns, port, value):
27
- self._time_ns = time_ns
28
- self._port = port
29
- self._value = value
29
+ def __init__(self, time_ns: int, port: PortOutDelta, value: int | str | None):
30
+ self._time_ns: int = time_ns
31
+ self._port: PortOutDelta = port
32
+ self._value: int | str | None = value
30
33
 
31
34
  @property
32
- def time_ns(self):
35
+ def time_ns(self) -> int:
33
36
  """Get the simulation time (ns) of this event"""
34
37
  return self._time_ns
35
38
 
36
39
  @property
37
- def port(self):
40
+ def port(self) -> PortOutDelta:
38
41
  """Get the port of this event"""
39
42
  return self._port
40
43
 
41
44
  @property
42
- def value(self):
45
+ def value(self) -> int | str | None:
43
46
  """Get the delta cycle value of this event"""
44
47
  return self._value
45
48
 
46
- def is_same_event(self, port):
49
+ def is_same_event(self, port: PortOutDelta):
47
50
  """Return True if the in the event is the same as"""
48
51
  return port == self._port
49
52
 
50
- def update(self, time_ns, value):
53
+ def update(self, time_ns: int, value: int | str | None):
51
54
  """Update the event with a new time (ns) and a new value"""
52
55
  self._time_ns = time_ns
53
56
  self._value = value
54
57
 
55
- def __lt__(self, other):
58
+ def __lt__(self, other) -> bool:
56
59
  return other.time_ns > self.time_ns
57
60
 
58
61
 
59
62
  class Circuit:
60
63
  """Class thay handles the circuit simulation"""
61
64
 
62
- def __init__(self, name=None, vcd=None):
63
- self._components = {}
64
- self._circuit_events = []
65
- self._name = name
66
- self._time_ns = 0
67
- self._folder = None
65
+ def __init__(self, name: str | None = None, vcd: str | None = None):
66
+ self._components: dict[str, Component] = {}
67
+ self._circuit_events: list[CircuitEvent] = []
68
+ self._name: str | None = name
69
+ self._time_ns: int = 0
70
+ self._folder: str | None = None
71
+ self._vcd: WavesWriter | None = None
68
72
 
69
73
  if vcd is not None:
70
74
  self._vcd = WavesWriter(filename=vcd)
71
- else:
72
- self._vcd = None
73
75
 
74
76
  @property
75
- def name(self):
77
+ def name(self) -> str | None:
76
78
  """Get the circuit name"""
77
79
  return self._name
78
80
 
79
81
  @property
80
- def time_ns(self):
82
+ def time_ns(self) -> int:
81
83
  """Get the current simulation time (ns)"""
82
84
  return self._time_ns
83
85
 
84
86
  @property
85
- def components(self):
87
+ def components(self) -> list[Component]:
86
88
  """Get the components in this circuit"""
87
89
  comp_array = []
88
90
  for _, comp in self._components.items():
89
91
  comp_array.append(comp)
90
92
  return comp_array
91
93
 
92
- def load_path(self, path):
94
+ def load_path(self, path) -> str:
93
95
  """Get the load path relative to the circuit path"""
94
96
  if self._folder is not None:
95
97
  return self._folder + "/" + path
96
98
  return path
97
99
 
98
- def store_path(self, path):
100
+ def store_path(self, path) -> str:
99
101
  """Get the store path relative to the circuit path"""
100
102
  if self._folder is not None:
101
103
  return os.path.relpath(path, self._folder)
102
104
  return path
103
105
 
104
- def delete_component(self, component):
106
+ def delete_component(self, component: Component):
105
107
  """Delete a component from the circuit"""
106
108
  del self._components[component.name()]
107
109
  component.remove_connections()
108
110
 
109
- def get_toplevel_components(self):
111
+ def get_toplevel_components(self) -> list[Component]:
110
112
  """Get toplevel components in the circuit"""
111
113
  toplevel_components = []
112
114
  for _, comp in self._components.items():
@@ -157,7 +159,7 @@ class Circuit:
157
159
  for port in comp.ports:
158
160
  self._vcd.write(port, self._time_ns)
159
161
 
160
- def _time_to_ns(self, s=None, ms=None, us=None, ns=None):
162
+ def _time_to_ns(self, s=None, ms=None, us=None, ns=None) -> int:
161
163
  time_ns = 0
162
164
  time_ns += s * 1e9 if s is not None else 0
163
165
  time_ns += ms * 1e6 if ms is not None else 0
@@ -168,7 +170,7 @@ class Circuit:
168
170
  def __exit__(self, exc_type, exc_value, exc_traceback):
169
171
  self._vcd.close()
170
172
 
171
- def process_single_event(self, stop_time_ns=None):
173
+ def process_single_event(self, stop_time_ns=None) -> Tuple[bool, bool]:
172
174
  """
173
175
  Process one simulation event
174
176
  Return False if ther are now events of if the stop_time has passed
@@ -190,13 +192,20 @@ class Circuit:
190
192
  self._vcd.write(event.port, self._time_ns)
191
193
  return True, toplevel
192
194
 
193
- def _is_toplevel_event(self):
195
+ def _is_toplevel_event(self) -> bool:
194
196
  if len(self._circuit_events) == 0:
195
197
  return False
196
198
  event = self._circuit_events[0]
197
199
  return event.port.parent().is_toplevel()
198
200
 
199
- def run(self, s=None, ms=None, us=None, ns=None, single_step=False):
201
+ def run(
202
+ self,
203
+ s: int | None = None,
204
+ ms: int | None = None,
205
+ us: int | None = None,
206
+ ns: int | None = None,
207
+ single_step: bool = False,
208
+ ) -> bool:
200
209
  """Run simulation for a period of time"""
201
210
  stop_time_ns = self._time_ns + self._time_to_ns(s=s, ms=ms, us=us, ns=ns)
202
211
  single_step_stop = False
@@ -210,13 +219,19 @@ class Circuit:
210
219
  self._time_ns = max(self._time_ns, stop_time_ns)
211
220
  return single_step_stop
212
221
 
213
- def run_until(self, s=None, ms=None, us=None, ns=None):
222
+ def run_until(
223
+ self,
224
+ s: int | None = None,
225
+ ms: int | None = None,
226
+ us: int | None = None,
227
+ ns: int | None = None,
228
+ ):
214
229
  """Run simulation until a specified time"""
215
230
  stop_time_ns = self._time_to_ns(s=s, ms=ms, us=us, ns=ns)
216
231
  if stop_time_ns >= self._time_ns:
217
232
  self.run(ns=stop_time_ns - self._time_ns)
218
233
 
219
- def add_event(self, port, value, propagation_delay_ns):
234
+ def add_event(self, port: PortOutDelta, value: int | str | None, propagation_delay_ns: int):
220
235
  """Add delta cycle event, this will also write values to .vcd file"""
221
236
  event_time_ns = self._time_ns + propagation_delay_ns
222
237
  # print(f"Add event {port.parent().name()}:{port.name()} => {value}")
@@ -226,7 +241,7 @@ class Circuit:
226
241
  return
227
242
  self._circuit_events.append(CircuitEvent(event_time_ns, port, value))
228
243
 
229
- def add_component(self, component):
244
+ def add_component(self, component: Component):
230
245
  """Add component to circuit"""
231
246
  name_id = 1
232
247
  namebase = component.name()
@@ -235,21 +250,21 @@ class Circuit:
235
250
  name_id += 1
236
251
  self._components[component.name()] = component
237
252
 
238
- def change_component_name(self, component, name):
253
+ def change_component_name(self, component: Component, name: str):
239
254
  """Change component name"""
240
255
  comp = self._components[component.name()]
241
256
  del self._components[component.name()]
242
257
  comp.set_name(name, update_circuit=False)
243
258
  self.add_component(comp)
244
259
 
245
- def get_component(self, component_name):
260
+ def get_component(self, component_name: str) -> Component:
246
261
  """Get component witgh 'component_name'"""
247
262
  comp = self._components.get(component_name)
248
263
  if comp is not None:
249
264
  return comp
250
265
  raise CircuitError(f"Component '{component_name}' not found")
251
266
 
252
- def to_dataclass(self, folder=None):
267
+ def to_dataclass(self, folder: str | None = None) -> CircuitDataClass:
253
268
  """Generate dict from circuit, used when storing circuit"""
254
269
  if self._name is None:
255
270
  raise CircuitError("Circuit must have a name")
@@ -257,8 +272,12 @@ class Circuit:
257
272
  return CircuitDataClass.from_circuit(self)
258
273
 
259
274
  def from_dataclass(
260
- self, circuit_dc, folder=None, component_exceptions=True, connect_exceptions=True
261
- ):
275
+ self,
276
+ circuit_dc: CircuitDataClass,
277
+ folder: str | None = None,
278
+ component_exceptions: bool = True,
279
+ connect_exceptions: bool = True,
280
+ ) -> list[str]:
262
281
  """Clear circuit and add components from dict"""
263
282
  self._folder = folder
264
283
  self.clear()
@@ -285,12 +304,12 @@ class Circuit:
285
304
 
286
305
  return exception_str_list
287
306
 
288
- def to_json_file(self, filename):
307
+ def to_json_file(self, filename: str):
289
308
  """Store circuit in json file"""
290
309
  circuitfile_dc = CircuitFileDataClass(circuit=self.to_dataclass())
291
310
  circuitfile_dc.save(filename)
292
311
 
293
- def from_json_file(self, filename, folder=None):
312
+ def from_json_file(self, filename: str, folder: str | None = None):
294
313
  """Load circuit from json file"""
295
314
  file_dc = CircuitFileDataClass.load(filename)
296
315
  self.from_dataclass(file_dc.circuit, folder)
@@ -1,35 +1,46 @@
1
- # Copyright (c) Fredrik Andersson, 2023-2024
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
2
  # All rights reserved
3
3
 
4
4
  """
5
5
  Module that handles the creation of vcd files
6
6
  """
7
7
 
8
+ import io
9
+ from typing import Any, Tuple
10
+
8
11
  from vcd import VCDWriter
9
12
 
13
+ from .components.atoms import Port
14
+
10
15
 
11
16
  class WavesWriter:
12
17
  """Class that handles the creation of vcd files"""
13
18
 
14
- def __init__(self, filename):
15
- self._vcd_name = filename
16
- self._vcd_file = None
17
- self._vcd_writer = None
18
- self._vcd_dict = {}
19
+ def __init__(self, filename: str):
20
+ self._vcd_name: str = filename
21
+ self._vcd_file: io.TextIOWrapper | None = None
22
+ self._vcd_writer: VCDWriter | None = None
23
+ self._vcd_dict: dict[str, Any] = {}
19
24
 
20
- def init(self, port_info):
25
+ def init(self, port_info: list[Tuple[str, str, int]]):
21
26
  """Initialize vcd writer"""
22
27
  if self._vcd_file is not None or self._vcd_writer is not None:
23
28
  self.close()
24
29
  self._vcd_file = open(self._vcd_name, mode="w", encoding="utf-8")
30
+ if self._vcd_file is None:
31
+ raise RuntimeError("VCD file is None")
25
32
  self._vcd_writer = VCDWriter(self._vcd_file, timescale="1 ns", date="today")
26
33
  for port_path, port_name, port_width in port_info:
27
34
  var = self._vcd_writer.register_var(port_path, port_name, "wire", size=port_width)
28
35
  self._vcd_dict[f"{port_path}.{port_name}"] = var
29
36
  self._vcd_file.flush()
30
37
 
31
- def write(self, port, time_ns):
38
+ def write(self, port: Port, time_ns: int):
32
39
  """Write port value to vcd file"""
40
+ if self._vcd_file is None:
41
+ raise RuntimeError("VCD file is None")
42
+ if self._vcd_writer is None:
43
+ raise RuntimeError("VCD Writer is None")
33
44
  for wired_port in port.get_wired_ports_recursive():
34
45
  var = self._vcd_dict.get(f"{wired_port.path()}.{wired_port.name()}")
35
46
  if var is None:
@@ -3,15 +3,15 @@
3
3
 
4
4
  """Label Wire components module"""
5
5
 
6
- from .atoms import Component, DigsimException, PortWire
6
+ from .atoms import Component, DigsimException, PortIn, PortWire
7
7
 
8
8
 
9
9
  class _LabelWireStorage:
10
10
  """Singleton class with label wires"""
11
11
 
12
12
  _instance = None
13
- _wire_drivers = {}
14
- _wire_sinks = {}
13
+ _wire_drivers: dict[str, PortWire] = {}
14
+ _wire_sinks: dict[str, PortIn] = {}
15
15
 
16
16
  def __new__(cls):
17
17
  if cls._instance is None:
@@ -1,4 +1,4 @@
1
- # Copyright (c) Fredrik Andersson, 2023
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
2
  # All rights reserved
3
3
 
4
4
  """All classes within digsim.circuit.components.atoms namespace"""
@@ -11,6 +11,7 @@ from ._component import ( # noqa: F401
11
11
  )
12
12
  from ._digsim_exception import DigsimException # noqa: F401
13
13
  from ._port import ( # noqa: F401
14
+ Port,
14
15
  PortConnectionError,
15
16
  PortIn,
16
17
  PortMultiBitWire,
@@ -1,12 +1,16 @@
1
- # Copyright (c) Fredrik Andersson, 2023-2024
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
2
  # All rights reserved
3
3
 
4
4
  """This module contains the base classes for all component types"""
5
5
 
6
+ from __future__ import annotations
7
+
6
8
  import abc
7
9
  import copy
10
+ from typing import Callable
8
11
 
9
12
  from ._digsim_exception import DigsimException
13
+ from ._port import Port
10
14
 
11
15
 
12
16
  class ComponentException(DigsimException):
@@ -16,15 +20,14 @@ class ComponentException(DigsimException):
16
20
  class Component(abc.ABC):
17
21
  """The component base class"""
18
22
 
19
- def __init__(self, circuit, name=None, display_name=None):
23
+ def __init__(self, circuit, name: str | None = None, display_name: str | None = None):
20
24
  self._circuit = circuit
21
- self._name = name or self.__class__.__name__
22
- self._parent = None
23
- self._ports = []
24
- self._edge_detect_dict = {}
25
+ self._name: str = name or self.__class__.__name__
26
+ self._parent: Component | None = None
27
+ self._ports: list[Port] = []
25
28
  self._circuit.add_component(self)
26
- self._display_name = display_name or self.__class__.__name__
27
- self._parameters = {}
29
+ self._display_name: str = display_name or self.__class__.__name__
30
+ self._parameters: dict[str, int | str | bool] = {}
28
31
 
29
32
  def init(self):
30
33
  """Initialize port, will be called when circuit is initialized"""
@@ -37,15 +40,15 @@ class Component(abc.ABC):
37
40
  def clear(self):
38
41
  """Remove static state within the component class"""
39
42
 
40
- def parameter_set(self, parameter, value):
43
+ def parameter_set(self, parameter: str, value: int | str | bool):
41
44
  """Set component parameter"""
42
45
  self._parameters[parameter] = value
43
46
 
44
- def parameter_get(self, parameter):
47
+ def parameter_get(self, parameter: str) -> int | str | bool:
45
48
  """Get component parameter"""
46
49
  return self._parameters[parameter]
47
50
 
48
- def add_port(self, port):
51
+ def add_port(self, port: Port):
49
52
  """
50
53
  Add port to component,
51
54
  also add a 'portname' variable to the component with help of the 'self.__dict__'
@@ -59,7 +62,7 @@ class Component(abc.ABC):
59
62
  """
60
63
  self._ports = []
61
64
 
62
- def path(self):
65
+ def path(self) -> str:
63
66
  """Get component path"""
64
67
  if self._parent is not None:
65
68
  return f"{self._parent.path()}.{self.name()}"
@@ -70,22 +73,22 @@ class Component(abc.ABC):
70
73
  """Get component ports"""
71
74
  return self._ports
72
75
 
73
- def _get_ports(self, output):
76
+ def _get_ports(self, output) -> list[Port]:
74
77
  sel_ports = []
75
78
  for port in self._ports:
76
79
  if port.is_output() == output:
77
80
  sel_ports.append(port)
78
81
  return sel_ports
79
82
 
80
- def inports(self):
83
+ def inports(self) -> list[Port]:
81
84
  """Get component input ports"""
82
85
  return self._get_ports(False)
83
86
 
84
- def outports(self):
87
+ def outports(self) -> list[Port]:
85
88
  """Get component output ports"""
86
89
  return self._get_ports(True)
87
90
 
88
- def port(self, portname):
91
+ def port(self, portname: str) -> Port:
89
92
  """Get port with name 'portname'"""
90
93
  for port in self._ports:
91
94
  if port.name() == portname:
@@ -97,46 +100,46 @@ class Component(abc.ABC):
97
100
  """Get the circuit for the current component"""
98
101
  return self._circuit
99
102
 
100
- def name(self):
103
+ def name(self) -> str:
101
104
  """Get the component name"""
102
105
  return self._name
103
106
 
104
- def set_name(self, name, update_circuit=True):
107
+ def set_name(self, name: str, update_circuit: bool = True):
105
108
  """Set the component name"""
106
109
  if update_circuit:
107
110
  self.circuit.change_component_name(self, name)
108
111
  else:
109
112
  self._name = name
110
113
 
111
- def display_name(self):
114
+ def display_name(self) -> str:
112
115
  """Get the component display name"""
113
116
  return self._display_name
114
117
 
115
- def set_display_name(self, display_name):
118
+ def set_display_name(self, display_name: str):
116
119
  """Set the component display name"""
117
120
  self._display_name = display_name
118
121
 
119
122
  @property
120
- def parent(self):
123
+ def parent(self) -> Component | None:
121
124
  """Get parent component"""
122
125
  return self._parent
123
126
 
124
- def is_toplevel(self):
125
- """Return True if this component is a toplevel component"""
126
- return self._parent is None
127
-
128
127
  @parent.setter
129
- def parent(self, parent):
128
+ def parent(self, parent: Component):
130
129
  """Set component parent"""
131
130
  self._parent = parent
132
131
 
132
+ def is_toplevel(self) -> bool:
133
+ """Return True if this component is a toplevel component"""
134
+ return self._parent is None
135
+
133
136
  @property
134
137
  def wire(self):
135
138
  """Property needed to be able to have a setter"""
136
139
  raise ComponentException(f"Cannot get wire for component '{self.display_name}'")
137
140
 
138
141
  @wire.setter
139
- def wire(self, port):
142
+ def wire(self, port: Port):
140
143
  """Some components have a single output port, they can wired at component level"""
141
144
  self.outports()[0].wire = port
142
145
 
@@ -146,14 +149,14 @@ class Component(abc.ABC):
146
149
  def remove_connections(self):
147
150
  """Remove component connections"""
148
151
  for src_port in self.outports():
149
- for dst_port in src_port.get_wires():
152
+ for dst_port in src_port.wired_ports:
150
153
  dst_port.set_driver(None)
151
154
 
152
155
  for dst_port in self.inports():
153
156
  if dst_port.has_driver():
154
157
  dst_port.get_driver().disconnect(dst_port)
155
158
 
156
- def add_event(self, port, value, delay_ns):
159
+ def add_event(self, port: Port, value: int, delay_ns: int):
157
160
  """Add delta cycle event"""
158
161
  self.circuit.add_event(port, value, delay_ns)
159
162
 
@@ -166,12 +169,12 @@ class Component(abc.ABC):
166
169
  return comp_str
167
170
 
168
171
  @property
169
- def has_action(self):
172
+ def has_action(self) -> bool:
170
173
  """Return True if this component is interactive"""
171
174
  return False
172
175
 
173
176
  @property
174
- def active(self):
177
+ def active(self) -> bool:
175
178
  """Return True if this component is active/activated ('on' for a switch for example)"""
176
179
  return False
177
180
 
@@ -185,7 +188,7 @@ class Component(abc.ABC):
185
188
  """Get component settings from dict"""
186
189
  raise ComponentException(f"No setup for component '{self.display_name}'")
187
190
 
188
- def settings_to_dict(self):
191
+ def settings_to_dict(self) -> dict[str, int | str | bool]:
189
192
  """Return component settings as a dict"""
190
193
  return copy.deepcopy(self._parameters)
191
194
 
@@ -194,7 +197,7 @@ class Component(abc.ABC):
194
197
  """Return parameters"""
195
198
  return {}
196
199
 
197
- def update_settings(self, settings):
200
+ def update_settings(self, settings: dict[str, int | str | bool]):
198
201
  """Update parameters from settings dict"""
199
202
  for setting, value in settings.items():
200
203
  self.parameter_set(setting, value)
@@ -222,9 +225,9 @@ class Component(abc.ABC):
222
225
  class MultiComponent(Component):
223
226
  """A component that holds one or several sub components"""
224
227
 
225
- def __init__(self, circuit, name):
228
+ def __init__(self, circuit, name: str):
226
229
  super().__init__(circuit, name)
227
- self._components = []
230
+ self._components: list[Component] = []
228
231
 
229
232
  def init(self):
230
233
  super().init()
@@ -253,11 +256,11 @@ class CallbackComponent(Component):
253
256
  objects when the component change value.
254
257
  """
255
258
 
256
- def __init__(self, circuit, name, callback=None):
259
+ def __init__(self, circuit, name: str, callback: Callable[[Component], None] | None = None):
257
260
  super().__init__(circuit, name)
258
261
  self._callback = callback
259
262
 
260
- def set_callback(self, callback):
263
+ def set_callback(self, callback: Callable[[Component], None]):
261
264
  """Set CallbackComponent callback function"""
262
265
  self._callback = callback
263
266