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,329 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """
5
+ Module that handles the circuit simulation of components
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import heapq
11
+ import pathlib
12
+ from typing import Tuple
13
+
14
+ from digsim.storage_model import CircuitDataClass, CircuitFileDataClass
15
+
16
+ from ._waves_writer import WavesWriter
17
+ from .components.atoms import VALUE_TYPE, Component, DigsimException, PortOutDelta
18
+
19
+
20
+ class CircuitError(DigsimException):
21
+ """A circuit error class"""
22
+
23
+
24
+ class CircuitEvent:
25
+ """
26
+ The circuit event class for storing the
27
+ delta events in the simulation.
28
+ """
29
+
30
+ def __init__(self, time_ns: int, port: PortOutDelta, value: VALUE_TYPE):
31
+ self._time_ns: int = time_ns
32
+ self._port: PortOutDelta = port
33
+ self._value: VALUE_TYPE = value
34
+
35
+ @property
36
+ def time_ns(self) -> int:
37
+ """Get the simulation time (ns) of this event"""
38
+ return self._time_ns
39
+
40
+ @property
41
+ def port(self) -> PortOutDelta:
42
+ """Get the port of this event"""
43
+ return self._port
44
+
45
+ @property
46
+ def value(self) -> VALUE_TYPE:
47
+ """Get the delta cycle value of this event"""
48
+ return self._value
49
+
50
+ def is_same_event(self, port: PortOutDelta):
51
+ """Return True if the in the event is the same as"""
52
+ return port == self._port
53
+
54
+ def update(self, time_ns: int, value: VALUE_TYPE):
55
+ """Update the event with a new time (ns) and a new value"""
56
+ self._time_ns = time_ns
57
+ self._value = value
58
+
59
+ def __lt__(self, other) -> bool:
60
+ return other.time_ns > self.time_ns
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
+
70
+
71
+ class Circuit:
72
+ """Class thay handles the circuit simulation"""
73
+
74
+ def __init__(self, name: str | None = None, vcd: str | None = None):
75
+ self._components: dict[str, Component] = {}
76
+ self._circuit_events: list[CircuitEvent] = []
77
+ self._events_by_port: dict[PortOutDelta, CircuitEvent] = {}
78
+ self._name: str | None = name
79
+ self._time_ns: int = 0
80
+ self._folder: str | None = None
81
+ self._vcd: WavesWriter | None = None
82
+
83
+ if vcd is not None:
84
+ self._vcd = WavesWriter(filename=vcd)
85
+
86
+ @property
87
+ def name(self) -> str | None:
88
+ """Get the circuit name"""
89
+ return self._name
90
+
91
+ @property
92
+ def time_ns(self) -> int:
93
+ """Get the current simulation time (ns)"""
94
+ return self._time_ns
95
+
96
+ @property
97
+ def components(self) -> list[Component]:
98
+ """Get the components in this circuit"""
99
+ return list(self._components.values())
100
+
101
+ def load_path(self, path) -> str:
102
+ """Get the load path relative to the circuit path"""
103
+ if self._folder is not None:
104
+ return self._folder + "/" + path
105
+ return path
106
+
107
+ def store_path(self, path) -> str:
108
+ """Get the store path relative to the circuit path"""
109
+ if self._folder is not None:
110
+ return str(
111
+ pathlib.Path(path).resolve().absolute().relative_to(pathlib.Path(self._folder))
112
+ )
113
+ return path
114
+
115
+ def delete_component(self, component: Component):
116
+ """Delete a component from the circuit"""
117
+ del self._components[component.name()]
118
+ component.remove_connections()
119
+
120
+ def get_toplevel_components(self) -> list[Component]:
121
+ """Get toplevel components in the circuit"""
122
+ return [comp for comp in self._components.values() if comp.is_toplevel()]
123
+
124
+ def init(self):
125
+ """Initialize circuit and components (and ports)"""
126
+ self._time_ns = 0
127
+ self._circuit_events = []
128
+ self._events_by_port = {}
129
+ if self._vcd is not None:
130
+ self._vcd_init()
131
+ for _, comp in self._components.items():
132
+ comp.init()
133
+ for _, comp in self._components.items():
134
+ comp.default_state()
135
+ self.run_until(ns=0) # Handle all time zero events
136
+
137
+ def clear(self):
138
+ """Remove all components"""
139
+ for _, comp in self._components.items():
140
+ comp.clear()
141
+ self._components = {}
142
+
143
+ def vcd(self, filename):
144
+ """Start wave collecting in a gtkwave .vcd file"""
145
+ if self._vcd is not None:
146
+ raise CircuitError("VCD already started")
147
+ self._vcd = WavesWriter(filename=filename)
148
+ self._vcd_init()
149
+
150
+ def vcd_close(self):
151
+ """Close gtkwave .vcd file"""
152
+ if self._vcd is not None:
153
+ self._vcd.close()
154
+ self._vcd = None
155
+
156
+ def _vcd_init(self):
157
+ port_info = []
158
+ for _, comp in self._components.items():
159
+ for port in comp.ports:
160
+ port_info.append((port.path(), port.name(), port.width))
161
+ self._vcd.init(port_info)
162
+
163
+ # Dump initial state in vcd
164
+ for _, comp in self._components.items():
165
+ for port in comp.ports:
166
+ self._vcd.write(port, self._time_ns)
167
+
168
+ def _time_to_ns(self, s=None, ms=None, us=None, ns=None) -> int:
169
+ time_ns = 0
170
+ time_ns += s * 1e9 if s is not None else 0
171
+ time_ns += ms * 1e6 if ms is not None else 0
172
+ time_ns += us * 1e3 if us is not None else 0
173
+ time_ns += ns if ns is not None else 0
174
+ return int(time_ns)
175
+
176
+ def __exit__(self, exc_type, exc_value, exc_traceback):
177
+ self._vcd.close()
178
+
179
+ def process_single_event(self, stop_time_ns=None) -> Tuple[bool, bool]:
180
+ """
181
+ Process one simulation event
182
+ Return False if ther are now events of if the stop_time has passed
183
+ """
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
210
+
211
+ def _is_toplevel_event(self) -> bool:
212
+ if len(self._circuit_events) == 0:
213
+ return False
214
+ event = self._circuit_events[0]
215
+ return event.port.parent().is_toplevel()
216
+
217
+ def run(
218
+ self,
219
+ s: int | None = None,
220
+ ms: int | None = None,
221
+ us: int | None = None,
222
+ ns: int | None = None,
223
+ single_step: bool = False,
224
+ ) -> bool:
225
+ """Run simulation for a period of time"""
226
+ stop_time_ns = self._time_ns + self._time_to_ns(s=s, ms=ms, us=us, ns=ns)
227
+ single_step_stop = False
228
+ while len(self._circuit_events) > 0 and self._time_ns <= stop_time_ns:
229
+ more_events, top_level_event = self.process_single_event(stop_time_ns)
230
+ if not more_events:
231
+ break
232
+ if single_step and top_level_event:
233
+ stop_time_ns = self._time_ns
234
+ single_step_stop = True
235
+ self._time_ns = max(self._time_ns, stop_time_ns)
236
+ return single_step_stop
237
+
238
+ def run_until(
239
+ self,
240
+ s: int | None = None,
241
+ ms: int | None = None,
242
+ us: int | None = None,
243
+ ns: int | None = None,
244
+ ):
245
+ """Run simulation until a specified time"""
246
+ stop_time_ns = self._time_to_ns(s=s, ms=ms, us=us, ns=ns)
247
+ if stop_time_ns >= self._time_ns:
248
+ self.run(ns=stop_time_ns - self._time_ns)
249
+
250
+ def add_event(self, port: PortOutDelta, value: VALUE_TYPE, propagation_delay_ns: int):
251
+ """Add delta cycle event, this will also write values to .vcd file"""
252
+ event_time_ns = self._time_ns + propagation_delay_ns
253
+ # print(f"Add event {port.parent().name()}:{port.name()} => {value}")
254
+ event = CircuitEvent(event_time_ns, port, value)
255
+ self._events_by_port[port] = event
256
+ heapq.heappush(self._circuit_events, event)
257
+
258
+ def add_component(self, component: Component):
259
+ """Add component to circuit"""
260
+ name_id = 1
261
+ namebase = component.name()
262
+ while component.name() in self._components:
263
+ component.set_name(f"{namebase}_{name_id}", update_circuit=False)
264
+ name_id += 1
265
+ self._components[component.name()] = component
266
+
267
+ def change_component_name(self, component: Component, name: str):
268
+ """Change component name"""
269
+ comp = self._components[component.name()]
270
+ del self._components[component.name()]
271
+ comp.set_name(name, update_circuit=False)
272
+ self.add_component(comp)
273
+
274
+ def get_component(self, component_name: str) -> Component:
275
+ """Get component witgh 'component_name'"""
276
+ comp = self._components.get(component_name)
277
+ if comp is not None:
278
+ return comp
279
+ raise CircuitError(f"Component '{component_name}' not found")
280
+
281
+ def to_dataclass(self, folder: str | None = None) -> CircuitDataClass:
282
+ """Generate dict from circuit, used when storing circuit"""
283
+ if self._name is None:
284
+ raise CircuitError("Circuit must have a name")
285
+ self._folder = folder
286
+ return CircuitDataClass.from_circuit(self)
287
+
288
+ def from_dataclass(
289
+ self,
290
+ circuit_dc: CircuitDataClass,
291
+ folder: str | None = None,
292
+ component_exceptions: bool = True,
293
+ connect_exceptions: bool = True,
294
+ ) -> list[str]:
295
+ """Clear circuit and add components from dict"""
296
+ self._folder = folder
297
+ self.clear()
298
+
299
+ exception_str_list = []
300
+ for component in circuit_dc.components:
301
+ try:
302
+ component.create(self)
303
+ except DigsimException as exc:
304
+ if component_exceptions:
305
+ raise exc
306
+ exception_str_list.append(f"{str(exc.__class__.__name__)}:{str(exc)}")
307
+ except FileNotFoundError as exc:
308
+ if component_exceptions:
309
+ raise exc
310
+ exception_str_list.append(f"{str(exc.__class__.__name__)}:{str(exc)}")
311
+ for wire in circuit_dc.wires:
312
+ try:
313
+ wire.connect(self)
314
+ except DigsimException as exc:
315
+ if connect_exceptions:
316
+ raise exc
317
+ exception_str_list.append(f"{exc.__class__.__name__}:{str(exc)}")
318
+
319
+ return exception_str_list
320
+
321
+ def to_json_file(self, filename: str):
322
+ """Store circuit in json file"""
323
+ circuitfile_dc = CircuitFileDataClass(circuit=self.to_dataclass())
324
+ circuitfile_dc.save(filename)
325
+
326
+ def from_json_file(self, filename: str, folder: str | None = None):
327
+ """Load circuit from json file"""
328
+ file_dc = CircuitFileDataClass.load(filename)
329
+ self.from_dataclass(file_dc.circuit, folder)
@@ -0,0 +1,61 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """
5
+ Module that handles the creation of vcd files
6
+ """
7
+
8
+ import io
9
+ from typing import Any, Tuple
10
+
11
+ from vcd import VCDWriter
12
+
13
+ from .components.atoms import Port
14
+
15
+
16
+ class WavesWriter:
17
+ """Class that handles the creation of vcd files"""
18
+
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] = {}
24
+
25
+ def init(self, port_info: list[Tuple[str, str, int]]):
26
+ """Initialize vcd writer"""
27
+ if self._vcd_file is not None or self._vcd_writer is not None:
28
+ self.close()
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")
32
+ self._vcd_writer = VCDWriter(self._vcd_file, timescale="1 ns", date="today")
33
+ for port_path, port_name, port_width in port_info:
34
+ var = self._vcd_writer.register_var(port_path, port_name, "wire", size=port_width)
35
+ self._vcd_dict[f"{port_path}.{port_name}"] = var
36
+ self._vcd_file.flush()
37
+
38
+ def write(self, port: Port, time_ns: int):
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")
44
+ for wired_port in port.get_wired_ports_recursive():
45
+ var = self._vcd_dict.get(f"{wired_port.path()}.{wired_port.name()}")
46
+ if var is None:
47
+ continue
48
+ self._vcd_writer.change(var, timestamp=time_ns, value=wired_port.value)
49
+ self._vcd_file.flush()
50
+
51
+ def close(self):
52
+ """Close vcd file"""
53
+ if self._vcd_writer is not None:
54
+ self._vcd_writer.close()
55
+ self._vcd_writer = None
56
+
57
+ if self._vcd_file is not None:
58
+ self._vcd_file.close()
59
+ self._vcd_file = None
60
+
61
+ self._vcd_dict = {}
@@ -0,0 +1,26 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """All classes within digsim.circuit.components namespace"""
5
+
6
+ from ._bus_bits import Bus2Wires, Wires2Bus # noqa: F401
7
+ from ._button import PushButton # noqa: F401
8
+ from ._buzzer import Buzzer # noqa: F401
9
+ from ._clock import Clock # noqa: F401
10
+ from ._dip_switch import DipSwitch # noqa: F401
11
+ from ._flip_flops import SRFF, ClockedJKFF, ClockedSRFF, ClockedTFF, FlipFlop # noqa: F401
12
+ from ._gates import AND, DFF, MUX, NAND, NOR, NOT, OR, SR, XOR # noqa: F401
13
+ from ._hexdigit import HexDigit # noqa: F401
14
+ from ._ic import IntegratedCircuit # noqa: F401
15
+ from ._label_wire import LabelWireIn, LabelWireOut # noqa: F401
16
+ from ._led import Led # noqa: F401
17
+ from ._logic_analyzer import LogicAnalyzer # noqa: F401
18
+ from ._mem64kbyte import Mem64kByte # noqa: F401
19
+ from ._memstdout import MemStdOut # noqa: F401
20
+ from ._note import Note # noqa: F401
21
+ from ._on_off_switch import OnOffSwitch # noqa: F401
22
+ from ._seven_segment import SevenSegment # noqa: F401
23
+ from ._static_level import GND, VDD # noqa: F401
24
+ from ._static_value import StaticValue # noqa: F401
25
+ from ._yosys_component import YosysComponent, YosysComponentException # noqa: F401
26
+ from .atoms import PortConnectionError # noqa: F401
@@ -0,0 +1,68 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """Module with the bus/bit <=> bus/bit components"""
5
+
6
+ from .atoms import Component, PortMultiBitWire
7
+
8
+
9
+ class Bus2Wires(Component):
10
+ """Bus to Bits splitter"""
11
+
12
+ def __init__(self, circuit, name=None, width=1, enable=None):
13
+ super().__init__(circuit, name)
14
+ self._bus = PortMultiBitWire(self, "bus", width=width)
15
+ self.add_port(self._bus)
16
+ self._enable = enable
17
+ if self._enable is None:
18
+ self._enable = list(range(width))
19
+
20
+ for bit_id in range(width):
21
+ if bit_id in self._enable:
22
+ bit_port = self._bus.get_bit(bit_id)
23
+ self.add_port(bit_port)
24
+
25
+ self.parameter_set("width", width)
26
+ self.parameter_set("enable", enable)
27
+
28
+ @classmethod
29
+ def get_parameters(cls):
30
+ return {
31
+ "width": {
32
+ "type": "int",
33
+ "min": 2,
34
+ "max": 32,
35
+ "default": 2,
36
+ "description": "Bus width",
37
+ },
38
+ "enable": {
39
+ "type": "width_bool",
40
+ "default": None,
41
+ "description": "Enable bit",
42
+ },
43
+ }
44
+
45
+
46
+ class Wires2Bus(Component):
47
+ """Bits to Bus merger"""
48
+
49
+ def __init__(self, circuit, name=None, width=1):
50
+ super().__init__(circuit, name)
51
+ self._bus = PortMultiBitWire(self, "bus", width=width, output=True)
52
+ self.add_port(self._bus)
53
+ for bit_id in range(width):
54
+ bit_port = self._bus.get_bit(bit_id)
55
+ self.add_port(bit_port)
56
+ self.parameter_set("width", width)
57
+
58
+ @classmethod
59
+ def get_parameters(cls):
60
+ return {
61
+ "width": {
62
+ "type": "int",
63
+ "min": 2,
64
+ "max": 32,
65
+ "default": 2,
66
+ "description": "Bus width",
67
+ },
68
+ }
@@ -0,0 +1,44 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """A PushButton component"""
5
+
6
+ from .atoms import CallbackComponent, PortOutImmediate
7
+
8
+
9
+ class PushButton(CallbackComponent):
10
+ """PushButton component class"""
11
+
12
+ def __init__(self, circuit, name=None):
13
+ super().__init__(circuit, name)
14
+ portout = PortOutImmediate(self, "O")
15
+ self.add_port(portout)
16
+ portout.update_parent(True)
17
+
18
+ def default_state(self):
19
+ self.release()
20
+
21
+ def push(self):
22
+ """Push pushbutton"""
23
+ self.O.value = 1
24
+
25
+ def release(self):
26
+ """Release pushbutton"""
27
+ self.O.value = 0
28
+
29
+ def reconfigure(self):
30
+ self.release()
31
+
32
+ @property
33
+ def has_action(self):
34
+ return True
35
+
36
+ @property
37
+ def active(self):
38
+ return self.O.value == 1
39
+
40
+ def onpress(self):
41
+ self.push()
42
+
43
+ def onrelease(self):
44
+ self.release()
@@ -0,0 +1,45 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """Module with the Buzzer component"""
5
+
6
+ from .atoms import CallbackComponent, PortIn
7
+
8
+
9
+ class Buzzer(CallbackComponent):
10
+ """Buzzer component class"""
11
+
12
+ TONE_TO_FREQUENCY = {
13
+ "A": 880,
14
+ "B": 987.77,
15
+ "C": 1046.5,
16
+ "D": 1174.66,
17
+ "E": 1318.51,
18
+ "F": 1396.91,
19
+ "G": 1567.98,
20
+ }
21
+
22
+ def __init__(self, circuit, name=None, tone="A"):
23
+ super().__init__(circuit, name)
24
+ self.add_port(PortIn(self, "I"))
25
+ self.parameter_set("tone", tone)
26
+
27
+ @property
28
+ def active(self):
29
+ return self.I.has_driver() and self.I.value == 1
30
+
31
+ def tone_frequency(self):
32
+ """Get frequency for tone"""
33
+ return self.TONE_TO_FREQUENCY[self.parameter_get("tone")]
34
+
35
+ @classmethod
36
+ def get_parameters(cls):
37
+ return {
38
+ "tone": {
39
+ "type": "list",
40
+ "items": ["A", "B", "C", "D", "E", "F", "G"],
41
+ "default": "A",
42
+ "description": "Buzzer tone",
43
+ "reconfigurable": True,
44
+ },
45
+ }
@@ -0,0 +1,54 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """Clock component module"""
5
+
6
+ from .atoms import CallbackComponent, PortOutDelta, PortOutImmediate
7
+
8
+
9
+ class Clock(CallbackComponent):
10
+ """Clock component class"""
11
+
12
+ def __init__(self, circuit, name=None, frequency=1):
13
+ super().__init__(circuit, name)
14
+ self._feedback = PortOutDelta(self, "feedback")
15
+ portout = PortOutImmediate(self, "O")
16
+ self.add_port(portout)
17
+ self._feedback.update_parent(True)
18
+ self.parameter_set("frequency", frequency)
19
+ self.reconfigure()
20
+
21
+ def default_state(self):
22
+ self.O.value = 0
23
+ self._feedback.value = 1
24
+
25
+ def update(self):
26
+ if self._feedback.value == 1:
27
+ self.O.value = 1
28
+ self._feedback.value = 0
29
+ else:
30
+ self.O.value = 0
31
+ self._feedback.value = 1
32
+ super().update()
33
+
34
+ def reconfigure(self):
35
+ frequency = self.parameter_get("frequency")
36
+ half_period_ns = int(1000000000 / (frequency * 2))
37
+ self._feedback.set_delay_ns(half_period_ns)
38
+
39
+ @property
40
+ def active(self):
41
+ return self.O.value == 1
42
+
43
+ @classmethod
44
+ def get_parameters(cls):
45
+ return {
46
+ "frequency": {
47
+ "type": "int",
48
+ "min": 1,
49
+ "max": 50,
50
+ "default": 1,
51
+ "description": "Clock frequency in Hz",
52
+ "reconfigurable": True,
53
+ },
54
+ }