digsim-logic-simulator 0.22.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. digsim/__init__.py +6 -0
  2. digsim/app/__main__.py +12 -0
  3. digsim/app/cli.py +68 -0
  4. digsim/app/gui/__init__.py +6 -0
  5. digsim/app/gui/_circuit_area.py +468 -0
  6. digsim/app/gui/_component_selection.py +154 -0
  7. digsim/app/gui/_main_window.py +163 -0
  8. digsim/app/gui/_top_bar.py +339 -0
  9. digsim/app/gui/_utils.py +26 -0
  10. digsim/app/gui/_warning_dialog.py +46 -0
  11. digsim/app/gui_objects/__init__.py +7 -0
  12. digsim/app/gui_objects/_bus_bit_object.py +94 -0
  13. digsim/app/gui_objects/_buzzer_object.py +97 -0
  14. digsim/app/gui_objects/_component_context_menu.py +79 -0
  15. digsim/app/gui_objects/_component_object.py +374 -0
  16. digsim/app/gui_objects/_component_port_item.py +63 -0
  17. digsim/app/gui_objects/_dip_switch_object.py +104 -0
  18. digsim/app/gui_objects/_gui_note_object.py +80 -0
  19. digsim/app/gui_objects/_gui_object_factory.py +80 -0
  20. digsim/app/gui_objects/_hexdigit_object.py +53 -0
  21. digsim/app/gui_objects/_image_objects.py +239 -0
  22. digsim/app/gui_objects/_label_object.py +97 -0
  23. digsim/app/gui_objects/_logic_analyzer_object.py +86 -0
  24. digsim/app/gui_objects/_seven_segment_object.py +131 -0
  25. digsim/app/gui_objects/_shortcut_objects.py +82 -0
  26. digsim/app/gui_objects/_yosys_object.py +32 -0
  27. digsim/app/gui_objects/images/AND.png +0 -0
  28. digsim/app/gui_objects/images/Analyzer.png +0 -0
  29. digsim/app/gui_objects/images/BUF.png +0 -0
  30. digsim/app/gui_objects/images/Buzzer.png +0 -0
  31. digsim/app/gui_objects/images/Clock.png +0 -0
  32. digsim/app/gui_objects/images/DFF.png +0 -0
  33. digsim/app/gui_objects/images/DIP_SWITCH.png +0 -0
  34. digsim/app/gui_objects/images/FlipFlop.png +0 -0
  35. digsim/app/gui_objects/images/IC.png +0 -0
  36. digsim/app/gui_objects/images/LED_OFF.png +0 -0
  37. digsim/app/gui_objects/images/LED_ON.png +0 -0
  38. digsim/app/gui_objects/images/MUX.png +0 -0
  39. digsim/app/gui_objects/images/NAND.png +0 -0
  40. digsim/app/gui_objects/images/NOR.png +0 -0
  41. digsim/app/gui_objects/images/NOT.png +0 -0
  42. digsim/app/gui_objects/images/ONE.png +0 -0
  43. digsim/app/gui_objects/images/OR.png +0 -0
  44. digsim/app/gui_objects/images/PB.png +0 -0
  45. digsim/app/gui_objects/images/Switch_OFF.png +0 -0
  46. digsim/app/gui_objects/images/Switch_ON.png +0 -0
  47. digsim/app/gui_objects/images/XNOR.png +0 -0
  48. digsim/app/gui_objects/images/XOR.png +0 -0
  49. digsim/app/gui_objects/images/YOSYS.png +0 -0
  50. digsim/app/gui_objects/images/ZERO.png +0 -0
  51. digsim/app/images/app_icon.png +0 -0
  52. digsim/app/model/__init__.py +6 -0
  53. digsim/app/model/_model.py +210 -0
  54. digsim/app/model/_model_components.py +162 -0
  55. digsim/app/model/_model_new_wire.py +57 -0
  56. digsim/app/model/_model_objects.py +155 -0
  57. digsim/app/model/_model_settings.py +35 -0
  58. digsim/app/model/_model_shortcuts.py +72 -0
  59. digsim/app/settings/__init__.py +8 -0
  60. digsim/app/settings/_component_settings.py +415 -0
  61. digsim/app/settings/_gui_settings.py +71 -0
  62. digsim/app/settings/_shortcut_dialog.py +39 -0
  63. digsim/circuit/__init__.py +7 -0
  64. digsim/circuit/_circuit.py +329 -0
  65. digsim/circuit/_waves_writer.py +61 -0
  66. digsim/circuit/components/__init__.py +26 -0
  67. digsim/circuit/components/_bus_bits.py +68 -0
  68. digsim/circuit/components/_button.py +44 -0
  69. digsim/circuit/components/_buzzer.py +45 -0
  70. digsim/circuit/components/_clock.py +54 -0
  71. digsim/circuit/components/_dip_switch.py +73 -0
  72. digsim/circuit/components/_flip_flops.py +99 -0
  73. digsim/circuit/components/_gates.py +246 -0
  74. digsim/circuit/components/_hexdigit.py +82 -0
  75. digsim/circuit/components/_ic.py +36 -0
  76. digsim/circuit/components/_label_wire.py +167 -0
  77. digsim/circuit/components/_led.py +18 -0
  78. digsim/circuit/components/_logic_analyzer.py +60 -0
  79. digsim/circuit/components/_mem64kbyte.py +42 -0
  80. digsim/circuit/components/_memstdout.py +37 -0
  81. digsim/circuit/components/_note.py +25 -0
  82. digsim/circuit/components/_on_off_switch.py +54 -0
  83. digsim/circuit/components/_seven_segment.py +28 -0
  84. digsim/circuit/components/_static_level.py +28 -0
  85. digsim/circuit/components/_static_value.py +44 -0
  86. digsim/circuit/components/_yosys_atoms.py +1353 -0
  87. digsim/circuit/components/_yosys_component.py +232 -0
  88. digsim/circuit/components/atoms/__init__.py +23 -0
  89. digsim/circuit/components/atoms/_component.py +280 -0
  90. digsim/circuit/components/atoms/_digsim_exception.py +8 -0
  91. digsim/circuit/components/atoms/_port.py +398 -0
  92. digsim/circuit/components/ic/74162.json +1331 -0
  93. digsim/circuit/components/ic/7448.json +834 -0
  94. digsim/storage_model/__init__.py +7 -0
  95. digsim/storage_model/_app.py +58 -0
  96. digsim/storage_model/_circuit.py +126 -0
  97. digsim/synth/__init__.py +6 -0
  98. digsim/synth/__main__.py +67 -0
  99. digsim/synth/_synthesis.py +156 -0
  100. digsim/utils/__init__.py +6 -0
  101. digsim/utils/_yosys_netlist.py +134 -0
  102. digsim_logic_simulator-0.22.0.dist-info/METADATA +140 -0
  103. digsim_logic_simulator-0.22.0.dist-info/RECORD +107 -0
  104. digsim_logic_simulator-0.22.0.dist-info/WHEEL +5 -0
  105. digsim_logic_simulator-0.22.0.dist-info/entry_points.txt +2 -0
  106. digsim_logic_simulator-0.22.0.dist-info/licenses/LICENSE.md +32 -0
  107. digsim_logic_simulator-0.22.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,398 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """This module contains the classes for all component ports"""
5
+
6
+ from __future__ import annotations
7
+
8
+ import abc
9
+ from typing import Literal, Optional, Union
10
+
11
+ from ._digsim_exception import DigsimException
12
+
13
+
14
+ VALUE_TYPE = Union[int, Literal["X"]]
15
+
16
+
17
+ class PortConnectionError(DigsimException):
18
+ """Exception for illegal connections"""
19
+
20
+
21
+ class Port(abc.ABC):
22
+ """The abstract base class for all ports"""
23
+
24
+ def __init__(self, parent, name: str, width: int = 1, output: bool = False):
25
+ self._parent = parent # The parent component
26
+ self._name: str = name # The name of this port
27
+ self._width: int = width # The bit-width of this port
28
+ self._output: bool = output # Is this port an output port
29
+ self._wired_ports: list[Port] = [] # The ports that this port drives
30
+ self._value: VALUE_TYPE = "X" # The value of this port
31
+ self._edge_detect_value: VALUE_TYPE = "X" # Last edge detect value
32
+ self.init() # Initialize the port
33
+
34
+ def init(self):
35
+ """Initialize port, will be called when compponent/circuit is initialized"""
36
+ self._value = "X"
37
+ self._edge_detect_value = "X"
38
+ self.update_wires("X")
39
+
40
+ @property
41
+ def wired_ports(self) -> list[Port]:
42
+ """Get wires from thisport"""
43
+ return self._wired_ports
44
+
45
+ @property
46
+ def value(self) -> VALUE_TYPE:
47
+ """Get the value of the port, can be "X" """
48
+ return self._value
49
+
50
+ @value.setter
51
+ def value(self, value: VALUE_TYPE):
52
+ """Set the value of the port"""
53
+ self.set_value(value)
54
+
55
+ @property
56
+ def width(self) -> int:
57
+ """Get the bit-width of the port"""
58
+ return self._width
59
+
60
+ @width.setter
61
+ def width(self, width: int):
62
+ """Set the bit-width of the port, will force a disconnect if it is connected"""
63
+ if width != self._width:
64
+ driver = self.get_driver()
65
+ if driver is not None:
66
+ driver.disconnect(self)
67
+ for port in self._wired_ports[:]:
68
+ self.disconnect(port)
69
+ self._width = width
70
+
71
+ @property
72
+ def wire(self):
73
+ """Need a property if to be able to have a setter..."""
74
+ raise PortConnectionError("Cannot get a wire")
75
+
76
+ @wire.setter
77
+ def wire(self, port: PortWire):
78
+ """Wire setter, connect this port to an input port (of same width)"""
79
+ if port.has_driver():
80
+ raise PortConnectionError(f"The port {port.path()}.{port.name()} already has a driver")
81
+ if self.width != port.width:
82
+ raise PortConnectionError("Cannot connect ports with different widths")
83
+ port.set_driver(self)
84
+ self._wired_ports.append(port)
85
+ port.value = self._value # Update wires when port is connected
86
+
87
+ def remove_wires(self):
88
+ """Remove wires port"""
89
+ for port in self._wired_ports:
90
+ port.set_driver(None)
91
+ self._wired_ports = []
92
+
93
+ def name(self) -> str:
94
+ """Get port name"""
95
+ return self._name
96
+
97
+ def path(self) -> str:
98
+ """Get port path, <component_name>...<component_name>"""
99
+ return self._parent.path()
100
+
101
+ def parent(self):
102
+ """Get parent component"""
103
+ return self._parent
104
+
105
+ def update_wires(self, value: VALUE_TYPE):
106
+ """Update connected wires (and self._value) with value"""
107
+ if self._value == value:
108
+ return
109
+ self._value = value
110
+ for port in self._wired_ports:
111
+ port.value = self._value
112
+
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)
128
+ return all_wired_ports
129
+
130
+ def is_output(self) -> bool:
131
+ """Return True if this port is an output port"""
132
+ return self._output
133
+
134
+ def is_input(self) -> bool:
135
+ """Return True if this port is an input port"""
136
+ return not self._output
137
+
138
+ def is_rising_edge(self) -> bool:
139
+ """
140
+ Return True if a rising edge has occured
141
+ Note: This function can only be called once per 'update'
142
+ """
143
+ rising_edge = False
144
+ if self.value == 1 and self._edge_detect_value == 0:
145
+ rising_edge = True
146
+ self._edge_detect_value = self.value
147
+ return rising_edge
148
+
149
+ def is_falling_edge(self) -> bool:
150
+ """
151
+ Return True if a falling edge has occured
152
+ Note: This function can only be called once per 'update'
153
+ """
154
+ falling_edge = False
155
+ if self.value == 0 and self._edge_detect_value == 1:
156
+ falling_edge = True
157
+ self._edge_detect_value = self.value
158
+ return falling_edge
159
+
160
+ @abc.abstractmethod
161
+ def set_value(self, value: VALUE_TYPE):
162
+ """Set value on port"""
163
+
164
+ @abc.abstractmethod
165
+ def set_driver(self, port: Port | None):
166
+ """Set port driver"""
167
+
168
+ @abc.abstractmethod
169
+ def has_driver(self) -> bool:
170
+ """Return True if port has driver"""
171
+
172
+ def get_driver(self):
173
+ """Get port driver"""
174
+
175
+ def can_add_wire(self) -> bool:
176
+ """Return True if it is possible to add a wire to this port"""
177
+ if self.is_output():
178
+ return True
179
+ if not self.has_driver():
180
+ return True
181
+ return False
182
+
183
+ def disconnect(self, port: Port):
184
+ """Disconnect port if it is wired"""
185
+ if port in self._wired_ports:
186
+ index = self._wired_ports.index(port)
187
+ del self._wired_ports[index]
188
+ port.set_driver(None)
189
+
190
+ def strval(self) -> str:
191
+ """Return value as string"""
192
+ if self.value == "X":
193
+ return "X"
194
+ if self.width > 1:
195
+ return f"0x{self.value:x}"
196
+ return f"{self.value}"
197
+
198
+ def __str__(self) -> str:
199
+ return f"{self._parent.name()}:{self._name}={self.value}"
200
+
201
+
202
+ class PortWire(Port):
203
+ """
204
+ The PortWire class:
205
+ * The port wire will instantaneously update the driven wires upon change.
206
+ """
207
+
208
+ def __init__(self, parent, name: str, width: int = 1, output: bool = False):
209
+ super().__init__(parent, name, width, output)
210
+ self._port_driver: Port | None = None # The port that drives this port
211
+
212
+ def set_value(self, value: VALUE_TYPE):
213
+ if value != self.value:
214
+ self.update_wires(value)
215
+
216
+ def set_driver(self, port: Port | None):
217
+ self._port_driver = port
218
+
219
+ def get_driver(self) -> Port | None:
220
+ """Get driver for port"""
221
+ return self._port_driver
222
+
223
+ def has_driver(self) -> bool:
224
+ """Return True if port has driver"""
225
+ return self._port_driver is not None
226
+
227
+
228
+ class PortIn(PortWire):
229
+ """
230
+ The PortIn class:
231
+ * The port wire will instantaneously update the driven wires upon change.
232
+ * The port will update the parent component upon change.
233
+ """
234
+
235
+ def __init__(self, parent, name: str, width: int = 1):
236
+ super().__init__(parent, name, width, output=False)
237
+
238
+ def set_value(self, value: VALUE_TYPE):
239
+ super().set_value(value)
240
+ self.parent().update()
241
+
242
+
243
+ class PortOutDelta(Port):
244
+ """
245
+ The PortOutDelta class:
246
+ * The port wire will update the driven wires after a delta cycle.
247
+ * The port will update the parent component if the _update_parent variable is set to true.
248
+ """
249
+
250
+ def __init__(self, parent, name: str, width: int = 1, delay_ns: int = 1):
251
+ super().__init__(parent, name, width, output=True)
252
+ self._delay_ns = delay_ns # Propagation delay for this port
253
+ self._update_parent = False # Should this port update parent on change
254
+
255
+ def update_parent(self, update_parent: bool):
256
+ """Set update parent valiable (True/False)"""
257
+ self._update_parent = update_parent
258
+
259
+ def set_delay_ns(self, delay_ns: int):
260
+ """Set port propagation delay"""
261
+ self._delay_ns = delay_ns
262
+
263
+ def set_value(self, value: VALUE_TYPE):
264
+ self.parent().add_event(self, value, self._delay_ns)
265
+
266
+ def update_port(self, value: VALUE_TYPE):
267
+ """Update the port output and the connected wires"""
268
+ self.update_wires(value)
269
+ if self._update_parent:
270
+ self.parent().update()
271
+
272
+ def delta_cycle(self, value: VALUE_TYPE):
273
+ """Handle the delta cycle event from the circuit"""
274
+ self.update_port(value)
275
+
276
+ def set_driver(self, port: Port | None):
277
+ raise PortConnectionError(f"The port {self.path()}.{self.name()} cannot be driven")
278
+
279
+ def get_driver(self) -> Port | None:
280
+ """Get driver for port, the output port has no driver"""
281
+ return None
282
+
283
+ def has_driver(self) -> bool:
284
+ """Return False since the port does not have a driver"""
285
+ return False
286
+
287
+
288
+ class PortOutImmediate(PortOutDelta):
289
+ """
290
+ The PortOutImmediate class:
291
+ * A special version of the PortOutDelta used for direct components (button/switch/value)
292
+ * The port driver will update the driven wires immediately
293
+ * The port will update the parent component if the _update_parent variable is set to true.
294
+ """
295
+
296
+ def __init__(self, parent, name: str, width: int = 1):
297
+ super().__init__(parent, name, width)
298
+
299
+ def set_value(self, value: VALUE_TYPE):
300
+ self.parent().add_event(self, value, 0)
301
+ super().update_port(value)
302
+
303
+ def delta_cycle(self, value: VALUE_TYPE):
304
+ """
305
+ Do nothing here, the event is just used to updates waves in Circuit class
306
+ """
307
+
308
+
309
+ class PortWireBit(PortWire):
310
+ """
311
+ The PortWireBit class is used when several bits should be collected into
312
+ a multi bit bus port.
313
+ The PortWireBit will update its parent (a PortMultiBitWire) upon change.
314
+ """
315
+
316
+ def __init__(self, parent, name: str, parent_port: PortMultiBitWire, output: bool):
317
+ super().__init__(parent, name, 1, output)
318
+ self._parent_port = parent_port
319
+
320
+ def set_value(self, value: VALUE_TYPE):
321
+ super().set_value(value)
322
+ self._parent_port.update_value_from_bits()
323
+
324
+ def get_parent_port(self) -> PortMultiBitWire:
325
+ """Get the parent PortMultiBitWire for this port"""
326
+ return self._parent_port
327
+
328
+
329
+ class PortMultiBitWire(Port):
330
+ """
331
+ The PortMultiWireBit class is used when several bits should be collected into
332
+ a multi bit bus port.
333
+ The PortWireMultiBit will add events to the circuit upon change to update vcd output.
334
+ """
335
+
336
+ def __init__(self, parent, name: str, width: int, output: bool = False):
337
+ self._port_driver: Port | None = None # The port that drives this port
338
+ self._bits = []
339
+ super().__init__(parent, name, width, output)
340
+ for bit_id in range(self.width):
341
+ self._bits.append(
342
+ PortWireBit(parent, f"{self.name()}_{bit_id}", self, output=not output)
343
+ )
344
+
345
+ def init(self):
346
+ super().init()
347
+ for bit in self._bits:
348
+ bit.init()
349
+
350
+ def set_value(self, value: VALUE_TYPE):
351
+ if isinstance(value, str):
352
+ return
353
+ for bit_id, bit in enumerate(self._bits):
354
+ bit_val = (value >> bit_id) & 1
355
+ bit.value = bit_val
356
+
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)
362
+ for bit in self._bits:
363
+ all_wired_ports.extend(bit.get_wired_ports_recursive(processed_ports))
364
+ return all_wired_ports
365
+
366
+ def set_driver(self, port: Port | None):
367
+ """Set port driver"""
368
+ self._port_driver = port
369
+
370
+ def get_driver(self) -> Port | None:
371
+ """Get port driver"""
372
+ return self._port_driver
373
+
374
+ def has_driver(self) -> bool:
375
+ """Return True if port has driver"""
376
+ return self._port_driver is not None
377
+
378
+ def get_bit(self, bit_id: int) -> Port:
379
+ """Get bit port"""
380
+ return self._bits[bit_id]
381
+
382
+ def update_value_from_bits(self):
383
+ """Update the port with the value of the bits"""
384
+ value = 0
385
+ for bit_id, bit in enumerate(self._bits):
386
+ if bit.value == "X":
387
+ self.update_wires("X")
388
+ return
389
+ value = value | (bit.value << bit_id)
390
+ self.update_wires(value)
391
+ # Send event just to update waves
392
+ self.parent().add_event(self, value, 0)
393
+
394
+ def delta_cycle(self, value: VALUE_TYPE):
395
+ """
396
+ Do nothing here, the event passed in 'update_value_from_bits'
397
+ is just used to updates waves in Circuit class
398
+ """