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.
- digsim/__init__.py +6 -0
- digsim/app/__main__.py +12 -0
- digsim/app/cli.py +68 -0
- digsim/app/gui/__init__.py +6 -0
- digsim/app/gui/_circuit_area.py +468 -0
- digsim/app/gui/_component_selection.py +154 -0
- digsim/app/gui/_main_window.py +163 -0
- digsim/app/gui/_top_bar.py +339 -0
- digsim/app/gui/_utils.py +26 -0
- digsim/app/gui/_warning_dialog.py +46 -0
- digsim/app/gui_objects/__init__.py +7 -0
- digsim/app/gui_objects/_bus_bit_object.py +94 -0
- digsim/app/gui_objects/_buzzer_object.py +97 -0
- digsim/app/gui_objects/_component_context_menu.py +79 -0
- digsim/app/gui_objects/_component_object.py +374 -0
- digsim/app/gui_objects/_component_port_item.py +63 -0
- digsim/app/gui_objects/_dip_switch_object.py +104 -0
- digsim/app/gui_objects/_gui_note_object.py +80 -0
- digsim/app/gui_objects/_gui_object_factory.py +80 -0
- digsim/app/gui_objects/_hexdigit_object.py +53 -0
- digsim/app/gui_objects/_image_objects.py +239 -0
- digsim/app/gui_objects/_label_object.py +97 -0
- digsim/app/gui_objects/_logic_analyzer_object.py +86 -0
- digsim/app/gui_objects/_seven_segment_object.py +131 -0
- digsim/app/gui_objects/_shortcut_objects.py +82 -0
- digsim/app/gui_objects/_yosys_object.py +32 -0
- digsim/app/gui_objects/images/AND.png +0 -0
- digsim/app/gui_objects/images/Analyzer.png +0 -0
- digsim/app/gui_objects/images/BUF.png +0 -0
- digsim/app/gui_objects/images/Buzzer.png +0 -0
- digsim/app/gui_objects/images/Clock.png +0 -0
- digsim/app/gui_objects/images/DFF.png +0 -0
- digsim/app/gui_objects/images/DIP_SWITCH.png +0 -0
- digsim/app/gui_objects/images/FlipFlop.png +0 -0
- digsim/app/gui_objects/images/IC.png +0 -0
- digsim/app/gui_objects/images/LED_OFF.png +0 -0
- digsim/app/gui_objects/images/LED_ON.png +0 -0
- digsim/app/gui_objects/images/MUX.png +0 -0
- digsim/app/gui_objects/images/NAND.png +0 -0
- digsim/app/gui_objects/images/NOR.png +0 -0
- digsim/app/gui_objects/images/NOT.png +0 -0
- digsim/app/gui_objects/images/ONE.png +0 -0
- digsim/app/gui_objects/images/OR.png +0 -0
- digsim/app/gui_objects/images/PB.png +0 -0
- digsim/app/gui_objects/images/Switch_OFF.png +0 -0
- digsim/app/gui_objects/images/Switch_ON.png +0 -0
- digsim/app/gui_objects/images/XNOR.png +0 -0
- digsim/app/gui_objects/images/XOR.png +0 -0
- digsim/app/gui_objects/images/YOSYS.png +0 -0
- digsim/app/gui_objects/images/ZERO.png +0 -0
- digsim/app/images/app_icon.png +0 -0
- digsim/app/model/__init__.py +6 -0
- digsim/app/model/_model.py +210 -0
- digsim/app/model/_model_components.py +162 -0
- digsim/app/model/_model_new_wire.py +57 -0
- digsim/app/model/_model_objects.py +155 -0
- digsim/app/model/_model_settings.py +35 -0
- digsim/app/model/_model_shortcuts.py +72 -0
- digsim/app/settings/__init__.py +8 -0
- digsim/app/settings/_component_settings.py +415 -0
- digsim/app/settings/_gui_settings.py +71 -0
- digsim/app/settings/_shortcut_dialog.py +39 -0
- digsim/circuit/__init__.py +7 -0
- digsim/circuit/_circuit.py +329 -0
- digsim/circuit/_waves_writer.py +61 -0
- digsim/circuit/components/__init__.py +26 -0
- digsim/circuit/components/_bus_bits.py +68 -0
- digsim/circuit/components/_button.py +44 -0
- digsim/circuit/components/_buzzer.py +45 -0
- digsim/circuit/components/_clock.py +54 -0
- digsim/circuit/components/_dip_switch.py +73 -0
- digsim/circuit/components/_flip_flops.py +99 -0
- digsim/circuit/components/_gates.py +246 -0
- digsim/circuit/components/_hexdigit.py +82 -0
- digsim/circuit/components/_ic.py +36 -0
- digsim/circuit/components/_label_wire.py +167 -0
- digsim/circuit/components/_led.py +18 -0
- digsim/circuit/components/_logic_analyzer.py +60 -0
- digsim/circuit/components/_mem64kbyte.py +42 -0
- digsim/circuit/components/_memstdout.py +37 -0
- digsim/circuit/components/_note.py +25 -0
- digsim/circuit/components/_on_off_switch.py +54 -0
- digsim/circuit/components/_seven_segment.py +28 -0
- digsim/circuit/components/_static_level.py +28 -0
- digsim/circuit/components/_static_value.py +44 -0
- digsim/circuit/components/_yosys_atoms.py +1353 -0
- digsim/circuit/components/_yosys_component.py +232 -0
- digsim/circuit/components/atoms/__init__.py +23 -0
- digsim/circuit/components/atoms/_component.py +280 -0
- digsim/circuit/components/atoms/_digsim_exception.py +8 -0
- digsim/circuit/components/atoms/_port.py +398 -0
- digsim/circuit/components/ic/74162.json +1331 -0
- digsim/circuit/components/ic/7448.json +834 -0
- digsim/storage_model/__init__.py +7 -0
- digsim/storage_model/_app.py +58 -0
- digsim/storage_model/_circuit.py +126 -0
- digsim/synth/__init__.py +6 -0
- digsim/synth/__main__.py +67 -0
- digsim/synth/_synthesis.py +156 -0
- digsim/utils/__init__.py +6 -0
- digsim/utils/_yosys_netlist.py +134 -0
- digsim_logic_simulator-0.22.0.dist-info/METADATA +140 -0
- digsim_logic_simulator-0.22.0.dist-info/RECORD +107 -0
- digsim_logic_simulator-0.22.0.dist-info/WHEEL +5 -0
- digsim_logic_simulator-0.22.0.dist-info/entry_points.txt +2 -0
- digsim_logic_simulator-0.22.0.dist-info/licenses/LICENSE.md +32 -0
- 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
|
+
}
|