digsim-logic-simulator 0.7.0__py3-none-any.whl → 0.9.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,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
 
@@ -1,8 +1,10 @@
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 classes for all component ports"""
5
5
 
6
+ from __future__ import annotations
7
+
6
8
  import abc
7
9
 
8
10
  from ._digsim_exception import DigsimException
@@ -15,14 +17,14 @@ class PortConnectionError(DigsimException):
15
17
  class Port(abc.ABC):
16
18
  """The abstract base class for all ports"""
17
19
 
18
- def __init__(self, parent, name, width=1, output=False):
20
+ def __init__(self, parent, name: str, width: int = 1, output: bool = False):
19
21
  self._parent = parent # The parent component
20
- self._name = name # The name of this port
21
- self._width = width # The bit-width of this port
22
- self._output = output # Is this port an output port
23
- self._wired_ports = [] # The ports that this port drives
24
- self._value = None # The value of this port
25
- self._edge_detect_value = "X" # Last edge detect value
22
+ self._name: str = name # The name of this port
23
+ self._width: int = width # The bit-width of this port
24
+ self._output: bool = output # Is this port an output port
25
+ self._wired_ports: list[Port] = [] # The ports that this port drives
26
+ self._value: int | str | None = None # The value of this port
27
+ self._edge_detect_value: int | str | None = "X" # Last edge detect value
26
28
  self.init() # Initialize the port
27
29
 
28
30
  def init(self):
@@ -32,27 +34,27 @@ class Port(abc.ABC):
32
34
  self.update_wires("X")
33
35
 
34
36
  @property
35
- def wired_ports(self):
37
+ def wired_ports(self) -> list[Port]:
36
38
  """Get wires from thisport"""
37
39
  return self._wired_ports
38
40
 
39
41
  @property
40
- def value(self):
42
+ def value(self) -> int | str | None:
41
43
  """Get the value of the port, can be "X" """
42
44
  return self._value
43
45
 
44
46
  @value.setter
45
- def value(self, value):
47
+ def value(self, value: int | str | None):
46
48
  """Set the value of the port"""
47
49
  self.set_value(value)
48
50
 
49
51
  @property
50
- def width(self):
52
+ def width(self) -> int:
51
53
  """Get the bit-width of the port"""
52
54
  return self._width
53
55
 
54
56
  @width.setter
55
- def width(self, width):
57
+ def width(self, width: int):
56
58
  """Set the bit-width of the port, will force a disconnect if it is connected"""
57
59
  if width != self._width:
58
60
  driver = self.get_driver()
@@ -68,7 +70,7 @@ class Port(abc.ABC):
68
70
  raise PortConnectionError("Cannot get a wire")
69
71
 
70
72
  @wire.setter
71
- def wire(self, port):
73
+ def wire(self, port: PortWire):
72
74
  """Wire setter, connect this port to an input port (of same width)"""
73
75
  if port.has_driver():
74
76
  raise PortConnectionError(f"The port {port.path()}.{port.name()} already has a driver")
@@ -84,11 +86,11 @@ class Port(abc.ABC):
84
86
  port.set_driver(None)
85
87
  self._wired_ports = []
86
88
 
87
- def name(self):
89
+ def name(self) -> str:
88
90
  """Get port name"""
89
91
  return self._name
90
92
 
91
- def path(self):
93
+ def path(self) -> str:
92
94
  """Get port path, <component_name>...<component_name>"""
93
95
  return self._parent.path()
94
96
 
@@ -96,7 +98,7 @@ class Port(abc.ABC):
96
98
  """Get parent component"""
97
99
  return self._parent
98
100
 
99
- def update_wires(self, value):
101
+ def update_wires(self, value: int | str | None):
100
102
  """Update connected wires (and self._value) with value"""
101
103
  if self._value == value:
102
104
  return
@@ -104,26 +106,22 @@ class Port(abc.ABC):
104
106
  for port in self._wired_ports:
105
107
  port.value = self._value
106
108
 
107
- def get_wires(self):
108
- """Get connected ports"""
109
- return self._wired_ports
110
-
111
- def get_wired_ports_recursive(self):
109
+ def get_wired_ports_recursive(self) -> list[Port]:
112
110
  """Get all connected ports (recursive)"""
113
111
  all_wired_ports = [self]
114
112
  for port in self._wired_ports:
115
113
  all_wired_ports.extend(port.get_wired_ports_recursive())
116
114
  return all_wired_ports
117
115
 
118
- def is_output(self):
116
+ def is_output(self) -> bool:
119
117
  """Return True if this port is an output port"""
120
118
  return self._output
121
119
 
122
- def is_input(self):
120
+ def is_input(self) -> bool:
123
121
  """Return True if this port is an input port"""
124
122
  return not self._output
125
123
 
126
- def is_rising_edge(self):
124
+ def is_rising_edge(self) -> bool:
127
125
  """
128
126
  Return True if a rising edge has occured
129
127
  Note: This function can only be called once per 'update'
@@ -134,7 +132,7 @@ class Port(abc.ABC):
134
132
  self._edge_detect_value = self.value
135
133
  return rising_edge
136
134
 
137
- def is_falling_edge(self):
135
+ def is_falling_edge(self) -> bool:
138
136
  """
139
137
  Return True if a falling edge has occured
140
138
  Note: This function can only be called once per 'update'
@@ -146,14 +144,21 @@ class Port(abc.ABC):
146
144
  return falling_edge
147
145
 
148
146
  @abc.abstractmethod
149
- def set_value(self, value):
147
+ def set_value(self, value: int | str | None):
150
148
  """Set value on port"""
151
149
 
152
150
  @abc.abstractmethod
153
- def set_driver(self, port):
151
+ def set_driver(self, port: Port | None):
154
152
  """Set port driver"""
155
153
 
156
- def can_add_wire(self):
154
+ @abc.abstractmethod
155
+ def has_driver(self) -> bool:
156
+ """Return True if port has driver"""
157
+
158
+ def get_driver(self):
159
+ """Get port driver"""
160
+
161
+ def can_add_wire(self) -> bool:
157
162
  """Return True if it is possible to add a wire to this port"""
158
163
  if self.is_output():
159
164
  return True
@@ -161,14 +166,14 @@ class Port(abc.ABC):
161
166
  return True
162
167
  return False
163
168
 
164
- def disconnect(self, port):
169
+ def disconnect(self, port: Port):
165
170
  """Disconnect port if it is wired"""
166
171
  if port in self._wired_ports:
167
172
  index = self._wired_ports.index(port)
168
173
  del self._wired_ports[index]
169
174
  port.set_driver(None)
170
175
 
171
- def strval(self):
176
+ def strval(self) -> str:
172
177
  """Return value as string"""
173
178
  if self.value == "X":
174
179
  return "X"
@@ -176,7 +181,7 @@ class Port(abc.ABC):
176
181
  return f"0x{self.value:x}"
177
182
  return f"{self.value}"
178
183
 
179
- def __str__(self):
184
+ def __str__(self) -> str:
180
185
  return f"{self._parent.name()}:{self._name}={self.value}"
181
186
 
182
187
 
@@ -186,22 +191,22 @@ class PortWire(Port):
186
191
  * The port wire will instantaneously update the driven wires upon change.
187
192
  """
188
193
 
189
- def __init__(self, parent, name, width=1, output=False):
194
+ def __init__(self, parent, name: str, width: int = 1, output: bool = False):
190
195
  super().__init__(parent, name, width, output)
191
- self._port_driver = None # The port that drives this port
196
+ self._port_driver: Port | None = None # The port that drives this port
192
197
 
193
- def set_value(self, value):
198
+ def set_value(self, value: int | str | None):
194
199
  if value != self.value:
195
200
  self.update_wires(value)
196
201
 
197
- def set_driver(self, port):
202
+ def set_driver(self, port: Port | None):
198
203
  self._port_driver = port
199
204
 
200
- def get_driver(self):
205
+ def get_driver(self) -> Port | None:
201
206
  """Get driver for port"""
202
207
  return self._port_driver
203
208
 
204
- def has_driver(self):
209
+ def has_driver(self) -> bool:
205
210
  """Return True if port has driver"""
206
211
  return self._port_driver is not None
207
212
 
@@ -213,10 +218,10 @@ class PortIn(PortWire):
213
218
  * The port will update the parent component upon change.
214
219
  """
215
220
 
216
- def __init__(self, parent, name, width=1):
221
+ def __init__(self, parent, name: str, width: int = 1):
217
222
  super().__init__(parent, name, width, output=False)
218
223
 
219
- def set_value(self, value):
224
+ def set_value(self, value: int | str | None):
220
225
  super().set_value(value)
221
226
  self.parent().update()
222
227
 
@@ -228,40 +233,40 @@ class PortOutDelta(Port):
228
233
  * The port will update the parent component if the _update_parent variable is set to true.
229
234
  """
230
235
 
231
- def __init__(self, parent, name, width=1, delay_ns=1):
236
+ def __init__(self, parent, name: str, width: int = 1, delay_ns: int = 1):
232
237
  super().__init__(parent, name, width, output=True)
233
238
  self._delay_ns = delay_ns # Propagation delay for this port
234
239
  self._update_parent = False # Should this port update parent on change
235
240
 
236
- def update_parent(self, update_parent):
241
+ def update_parent(self, update_parent: bool):
237
242
  """Set update parent valiable (True/False)"""
238
243
  self._update_parent = update_parent
239
244
 
240
- def set_delay_ns(self, delay_ns):
245
+ def set_delay_ns(self, delay_ns: int):
241
246
  """Set port propagation delay"""
242
247
  self._delay_ns = delay_ns
243
248
 
244
- def set_value(self, value):
249
+ def set_value(self, value: int | str | None):
245
250
  self.parent().add_event(self, value, self._delay_ns)
246
251
 
247
- def update_port(self, value):
252
+ def update_port(self, value: int | str | None):
248
253
  """Update the port output and the connected wires"""
249
254
  self.update_wires(value)
250
255
  if self._update_parent:
251
256
  self.parent().update()
252
257
 
253
- def delta_cycle(self, value):
258
+ def delta_cycle(self, value: int | str | None):
254
259
  """Handle the delta cycle event from the circuit"""
255
260
  self.update_port(value)
256
261
 
257
- def set_driver(self, port):
262
+ def set_driver(self, port: Port | None):
258
263
  raise PortConnectionError(f"The port {self.path()}.{self.name()} cannot be driven")
259
264
 
260
- def get_driver(self):
265
+ def get_driver(self) -> Port | None:
261
266
  """Get driver for port, the output port has no driver"""
262
267
  return None
263
268
 
264
- def has_driver(self):
269
+ def has_driver(self) -> bool:
265
270
  """Return False since the port does not have a driver"""
266
271
  return False
267
272
 
@@ -274,14 +279,14 @@ class PortOutImmediate(PortOutDelta):
274
279
  * The port will update the parent component if the _update_parent variable is set to true.
275
280
  """
276
281
 
277
- def __init__(self, parent, name, width=1):
282
+ def __init__(self, parent, name: str, width: int = 1):
278
283
  super().__init__(parent, name, width)
279
284
 
280
- def set_value(self, value):
285
+ def set_value(self, value: int | str | None):
281
286
  self.parent().add_event(self, value, 0)
282
287
  super().update_port(value)
283
288
 
284
- def delta_cycle(self, value):
289
+ def delta_cycle(self, value: int | str | None):
285
290
  """
286
291
  Do nothing here, the event is just used to updates waves in Circuit class
287
292
  """
@@ -294,15 +299,15 @@ class PortWireBit(PortWire):
294
299
  The PortWireBit will update its parent (a PortMultiBitWire) upon change.
295
300
  """
296
301
 
297
- def __init__(self, parent, name, parent_port, output):
302
+ def __init__(self, parent, name: str, parent_port: PortMultiBitWire, output: bool):
298
303
  super().__init__(parent, name, 1, output)
299
304
  self._parent_port = parent_port
300
305
 
301
- def set_value(self, value):
306
+ def set_value(self, value: int | str | None):
302
307
  super().set_value(value)
303
308
  self._parent_port.update_value_from_bits()
304
309
 
305
- def get_parent_port(self):
310
+ def get_parent_port(self) -> PortMultiBitWire:
306
311
  """Get the parent PortMultiBitWire for this port"""
307
312
  return self._parent_port
308
313
 
@@ -314,8 +319,8 @@ class PortMultiBitWire(Port):
314
319
  The PortWireMultiBit will add events to the circuit upon change to update vcd output.
315
320
  """
316
321
 
317
- def __init__(self, parent, name, width, output=False):
318
- self._port_driver = None # The port that drives this port
322
+ def __init__(self, parent, name: str, width: int, output: bool = False):
323
+ self._port_driver: Port | None = None # The port that drives this port
319
324
  self._bits = []
320
325
  super().__init__(parent, name, width, output)
321
326
  for bit_id in range(self.width):
@@ -328,32 +333,32 @@ class PortMultiBitWire(Port):
328
333
  for bit in self._bits:
329
334
  bit.init()
330
335
 
331
- def set_value(self, value):
332
- if value == "X":
336
+ def set_value(self, value: int | str | None):
337
+ if value is None or isinstance(value, str):
333
338
  return
334
339
  for bit_id, bit in enumerate(self._bits):
335
340
  bit_val = (value >> bit_id) & 1
336
341
  bit.value = bit_val
337
342
 
338
- def get_wired_ports_recursive(self):
343
+ def get_wired_ports_recursive(self) -> list[Port]:
339
344
  all_wired_ports = super().get_wired_ports_recursive()
340
345
  for bit in self._bits:
341
346
  all_wired_ports.extend(bit.get_wired_ports_recursive())
342
347
  return all_wired_ports
343
348
 
344
- def set_driver(self, port):
349
+ def set_driver(self, port: Port | None):
345
350
  """Set port driver"""
346
351
  self._port_driver = port
347
352
 
348
- def get_driver(self):
353
+ def get_driver(self) -> Port | None:
349
354
  """Get port driver"""
350
355
  return self._port_driver
351
356
 
352
- def has_driver(self):
357
+ def has_driver(self) -> bool:
353
358
  """Return True if port has driver"""
354
359
  return self._port_driver is not None
355
360
 
356
- def get_bit(self, bit_id):
361
+ def get_bit(self, bit_id: int) -> Port:
357
362
  """Get bit port"""
358
363
  return self._bits[bit_id]
359
364
 
@@ -370,7 +375,7 @@ class PortMultiBitWire(Port):
370
375
  # Send event just to update waves
371
376
  self.parent().add_event(self, value, 0)
372
377
 
373
- def delta_cycle(self, value):
378
+ def delta_cycle(self, value: int | str | None):
374
379
  """
375
380
  Do nothing here, the event passed in 'update_value_from_bits'
376
381
  is just used to updates waves in Circuit class