nortl 1.4.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.
- nortl/__init__.py +85 -0
- nortl/components/__init__.py +8 -0
- nortl/components/channel.py +132 -0
- nortl/components/timer.py +73 -0
- nortl/core/__init__.py +40 -0
- nortl/core/checker.py +135 -0
- nortl/core/common/__init__.py +4 -0
- nortl/core/common/access.py +25 -0
- nortl/core/common/debug.py +6 -0
- nortl/core/common/naming_helper.py +33 -0
- nortl/core/constructs/__init__.py +13 -0
- nortl/core/constructs/condition.py +143 -0
- nortl/core/constructs/fork_join.py +84 -0
- nortl/core/constructs/loop.py +138 -0
- nortl/core/engine.py +575 -0
- nortl/core/exceptions.py +139 -0
- nortl/core/manager/__init__.py +6 -0
- nortl/core/manager/scratch_manager.py +128 -0
- nortl/core/manager/signal_manager.py +71 -0
- nortl/core/modifiers.py +136 -0
- nortl/core/module.py +181 -0
- nortl/core/operations.py +834 -0
- nortl/core/parameter.py +88 -0
- nortl/core/process.py +451 -0
- nortl/core/protocols.py +628 -0
- nortl/core/renderers/__init__.py +0 -0
- nortl/core/renderers/operations/__init__.py +34 -0
- nortl/core/renderers/operations/arithmetics.py +38 -0
- nortl/core/renderers/operations/base.py +111 -0
- nortl/core/renderers/operations/comparison.py +44 -0
- nortl/core/renderers/operations/logic.py +38 -0
- nortl/core/renderers/operations/misc.py +26 -0
- nortl/core/renderers/operations/slice.py +30 -0
- nortl/core/signal.py +878 -0
- nortl/core/state.py +201 -0
- nortl/py.typed +0 -0
- nortl/renderer/__init__.py +5 -0
- nortl/renderer/mermaid_renderer.py +38 -0
- nortl/renderer/networkx_renderer.py +29 -0
- nortl/renderer/verilog_renderer.py +325 -0
- nortl/renderer/verilog_utils/__init__.py +6 -0
- nortl/renderer/verilog_utils/formatter.py +29 -0
- nortl/renderer/verilog_utils/process.py +226 -0
- nortl/renderer/verilog_utils/structural.py +146 -0
- nortl/renderer/verilog_utils/utils.py +23 -0
- nortl/utils/__init__.py +0 -0
- nortl/utils/parse_utils.py +37 -0
- nortl/utils/templates/testbench.sv +41 -0
- nortl/utils/test_wrapper.py +218 -0
- nortl/utils/type_aliases.py +15 -0
- nortl/verilog_library/__init__.py +74 -0
- nortl/verilog_library/nortl_clock_gate.sv +20 -0
- nortl/verilog_library/nortl_count_down_timer.sv +50 -0
- nortl/verilog_library/nortl_delay.sv +66 -0
- nortl/verilog_library/nortl_edge_detector.sv +34 -0
- nortl/verilog_library/nortl_sync.sv +28 -0
- nortl-1.4.0.dist-info/METADATA +105 -0
- nortl-1.4.0.dist-info/RECORD +60 -0
- nortl-1.4.0.dist-info/WHEEL +4 -0
- nortl-1.4.0.dist-info/licenses/LICENSE +11 -0
nortl/core/signal.py
ADDED
|
@@ -0,0 +1,878 @@
|
|
|
1
|
+
"""Signal definition."""
|
|
2
|
+
|
|
3
|
+
from abc import ABCMeta, abstractmethod
|
|
4
|
+
from collections import OrderedDict
|
|
5
|
+
from types import TracebackType
|
|
6
|
+
from typing import Dict, Final, Generic, Literal, Mapping, Optional, Sequence, Set, Tuple, Type, TypeVar, Union
|
|
7
|
+
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
from nortl.core.checker import StaticAccessChecker
|
|
11
|
+
from nortl.core.common import NamedEntity, StaticAccess
|
|
12
|
+
from nortl.core.exceptions import AccessAfterReleaseError, WriteViolationError
|
|
13
|
+
from nortl.core.modifiers import BaseModifier
|
|
14
|
+
from nortl.core.operations import OperationTrait
|
|
15
|
+
from nortl.core.protocols import (
|
|
16
|
+
ACCESS_CHECKS,
|
|
17
|
+
BIT_ORDER,
|
|
18
|
+
EVENT_TYPES,
|
|
19
|
+
SIGNAL_TYPES,
|
|
20
|
+
AssignmentTarget,
|
|
21
|
+
EngineProto,
|
|
22
|
+
ModuleInstanceProto,
|
|
23
|
+
ParameterProto,
|
|
24
|
+
Renderable,
|
|
25
|
+
SignalProto,
|
|
26
|
+
SignalSliceProto,
|
|
27
|
+
StaticAccessCheckerProto,
|
|
28
|
+
StaticAccessProto,
|
|
29
|
+
ThreadProto,
|
|
30
|
+
)
|
|
31
|
+
from nortl.utils.type_aliases import IntSlice
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
'ScratchSignal',
|
|
35
|
+
'Signal',
|
|
36
|
+
'SignalSlice',
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
T_Signal = TypeVar('T_Signal', SignalProto, SignalSliceProto)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ParameterizedEvent:
|
|
43
|
+
"""Wrapper object for a parametrized event."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, event: EVENT_TYPES):
|
|
46
|
+
self._parameter_dict: OrderedDict[str, Union[int, str, ParameterProto]] = OrderedDict()
|
|
47
|
+
self._event = event
|
|
48
|
+
|
|
49
|
+
def __repr__(self) -> str:
|
|
50
|
+
ret = str(self._event)
|
|
51
|
+
for k, v in self._parameter_dict.items():
|
|
52
|
+
ret += f'{k} {v}'
|
|
53
|
+
|
|
54
|
+
return ret
|
|
55
|
+
|
|
56
|
+
def __hash__(self) -> int:
|
|
57
|
+
return hash(repr(self))
|
|
58
|
+
|
|
59
|
+
def __eq__(self, other: object) -> bool:
|
|
60
|
+
if not isinstance(other, ParameterizedEvent):
|
|
61
|
+
raise NotImplementedError('Can not compare parameterized Events to other types!')
|
|
62
|
+
return repr(self) == repr(other)
|
|
63
|
+
|
|
64
|
+
def add_parameter(self, param: str, value: Union[int, str, ParameterProto]) -> None:
|
|
65
|
+
self._parameter_dict[param] = value
|
|
66
|
+
|
|
67
|
+
def get_parameter(self, param: str) -> Union[int, str, ParameterProto]:
|
|
68
|
+
return self._parameter_dict[param]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def pick_indexes(all_indexes: Sequence[int], index: Union[int, IntSlice]) -> Sequence[int]:
|
|
72
|
+
"""Pick indexes covered by this slice."""
|
|
73
|
+
if isinstance(index, slice):
|
|
74
|
+
# noRTL supports reversed indexes, to better match Verilog. They must be flipped here.
|
|
75
|
+
# In addition, the stop index is treated as inclusive
|
|
76
|
+
start = min(index.start, index.stop) # type: ignore[type-var]
|
|
77
|
+
stop = max(index.start, index.stop) + 1 # type: ignore[type-var, operator]
|
|
78
|
+
return all_indexes[start:stop]
|
|
79
|
+
else:
|
|
80
|
+
return (all_indexes[index],)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def list_indexes(index: Union[int, IntSlice]) -> Sequence[int]:
|
|
84
|
+
"""List all indexes covered by a slice."""
|
|
85
|
+
if isinstance(index, slice):
|
|
86
|
+
# noRTL supports reversed indexes, to better match Verilog. They must be flipped here.
|
|
87
|
+
# In addition, the stop index is treated as inclusive
|
|
88
|
+
start: int = min(index.start, index.stop) # type: ignore[assignment, type-var]
|
|
89
|
+
stop: int = max(index.start, index.stop) # type: ignore[assignment, type-var]
|
|
90
|
+
return tuple(range(start, stop + 1))
|
|
91
|
+
else:
|
|
92
|
+
return (index,)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def validate_slice(index: IntSlice) -> Tuple[int, int, BIT_ORDER]:
|
|
96
|
+
"""Validate a slice and return start and stop values sorted by size."""
|
|
97
|
+
# The stop value for the Python slice is treated as inclusive
|
|
98
|
+
start, stop, step = index.start, index.stop, index.step
|
|
99
|
+
|
|
100
|
+
if start is None:
|
|
101
|
+
raise ValueError('Missing start position for signal slice operation!')
|
|
102
|
+
if stop is None:
|
|
103
|
+
raise ValueError('Missing stop position for signal slice operation!')
|
|
104
|
+
if step is not None:
|
|
105
|
+
raise ValueError('Providing a step size is not supported for signal slice operation!')
|
|
106
|
+
|
|
107
|
+
return min(start, stop), max(start, stop), 'L:H' if stop > start else 'H:L'
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class _BaseSignal(OperationTrait, NamedEntity, metaclass=ABCMeta):
|
|
111
|
+
"""Abstract base class for signals."""
|
|
112
|
+
|
|
113
|
+
is_primitive: Final = True
|
|
114
|
+
|
|
115
|
+
def __init__(self, name: str):
|
|
116
|
+
super().__init__(name)
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def name(self) -> str:
|
|
120
|
+
"""Signal name."""
|
|
121
|
+
return self._name
|
|
122
|
+
|
|
123
|
+
# Abstract properties required by base methods
|
|
124
|
+
@property
|
|
125
|
+
@abstractmethod
|
|
126
|
+
def engine(self) -> EngineProto:
|
|
127
|
+
"""NoRTL engine that this signal belongs to."""
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
@abstractmethod
|
|
131
|
+
def width(self) -> Union[int, ParameterProto, Renderable]:
|
|
132
|
+
"""Signal width in bits."""
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
@abstractmethod
|
|
136
|
+
def escaped_name(self) -> str:
|
|
137
|
+
"""Name of signal with any special characters escaped."""
|
|
138
|
+
|
|
139
|
+
# Access control
|
|
140
|
+
@property
|
|
141
|
+
@abstractmethod
|
|
142
|
+
def read_accesses(self) -> Set[StaticAccessProto]:
|
|
143
|
+
"""Mutable set of read accesses to this signal."""
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
@abstractmethod
|
|
147
|
+
def write_accesses(self) -> Set[StaticAccessProto]:
|
|
148
|
+
"""Mutable set of write accesses to this signal."""
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
@abstractmethod
|
|
152
|
+
def last_read_access_thread(self) -> Optional[ThreadProto]:
|
|
153
|
+
"""Thread performing the last read access."""
|
|
154
|
+
|
|
155
|
+
@last_read_access_thread.setter
|
|
156
|
+
@abstractmethod
|
|
157
|
+
def last_read_access_thread(self, value: ThreadProto) -> None:
|
|
158
|
+
"""Thread performing the last read access."""
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
@abstractmethod
|
|
162
|
+
def last_write_access_thread(self) -> Optional[ThreadProto]:
|
|
163
|
+
"""Thread performing the last write access."""
|
|
164
|
+
|
|
165
|
+
@last_write_access_thread.setter
|
|
166
|
+
@abstractmethod
|
|
167
|
+
def last_write_access_thread(self, value: ThreadProto) -> None:
|
|
168
|
+
"""Thread performing the last write access."""
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
@abstractmethod
|
|
172
|
+
def access_checker(self) -> StaticAccessCheckerProto:
|
|
173
|
+
"""Static access checker."""
|
|
174
|
+
|
|
175
|
+
def write_access(self, ignore: Set[ACCESS_CHECKS] = set()) -> None:
|
|
176
|
+
"""Register write access from the current thread.
|
|
177
|
+
|
|
178
|
+
If the current thread differs from the last write acess, will invoke the access checker.
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
ExclusiveWriteError: If the signals was written by more than one thread.
|
|
182
|
+
NonIdenticalRWError: If the signals was written by one, and read from another thread.
|
|
183
|
+
"""
|
|
184
|
+
self.write_accesses.add(StaticAccess(self.engine.current_thread))
|
|
185
|
+
|
|
186
|
+
if self.engine.current_thread is not self.last_write_access_thread:
|
|
187
|
+
# Slow check is only executed, if the thread has changed.
|
|
188
|
+
self.access_checker.check(ignore=ignore)
|
|
189
|
+
|
|
190
|
+
self.last_write_access_thread = self.engine.current_thread
|
|
191
|
+
|
|
192
|
+
def read_access(self, ignore: Set[ACCESS_CHECKS] = set()) -> None:
|
|
193
|
+
"""Register read access from the current thread.
|
|
194
|
+
|
|
195
|
+
If the current thread differs from the last write acess, will invoke the access checker.
|
|
196
|
+
|
|
197
|
+
Raises:
|
|
198
|
+
ExclusiveReadError: If the signal was read from more than one thread.
|
|
199
|
+
NonIdenticalRWError: If the signals was written by one, and read from another thread.
|
|
200
|
+
"""
|
|
201
|
+
self.read_accesses.add(StaticAccess(self.engine.current_thread))
|
|
202
|
+
|
|
203
|
+
if self.engine.current_thread is not self.last_read_access_thread:
|
|
204
|
+
# Slow check is only executed, if the thread has changed.
|
|
205
|
+
self.access_checker.check(ignore=ignore)
|
|
206
|
+
|
|
207
|
+
self.last_read_access_thread = self.engine.current_thread
|
|
208
|
+
|
|
209
|
+
def free_access_from_thread(self, thread: ThreadProto) -> None:
|
|
210
|
+
"""This function disables all access checks that have their origin in the given thread.
|
|
211
|
+
|
|
212
|
+
It is to be used during fork for passing signals to a forked thread. In this case, the signal
|
|
213
|
+
is accessed (written) by the origin thread and handed off to the spawned thread.
|
|
214
|
+
|
|
215
|
+
Since parallel running behavior is described below the actual fork context, a colliding access will
|
|
216
|
+
happen after the fork-block has been executed. The collision will be shown once the origin thread
|
|
217
|
+
will access the signal while the forked thread has not ended.
|
|
218
|
+
"""
|
|
219
|
+
for access in self.write_accesses | self.read_accesses:
|
|
220
|
+
if access.thread == thread:
|
|
221
|
+
access.disable()
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class _AccessControlledSignal(_BaseSignal):
|
|
225
|
+
"""Intermediary class for signals that keep track their own access control.
|
|
226
|
+
|
|
227
|
+
This is used for signals and scratch signals.
|
|
228
|
+
"""
|
|
229
|
+
|
|
230
|
+
def __init__(self, name: str) -> None:
|
|
231
|
+
super().__init__(name)
|
|
232
|
+
|
|
233
|
+
self._write_accesses: Set[StaticAccessProto] = set()
|
|
234
|
+
self._read_accesses: Set[StaticAccessProto] = set()
|
|
235
|
+
self._last_read_access_thread: Optional[ThreadProto] = None
|
|
236
|
+
self._last_write_access_thread: Optional[ThreadProto] = None
|
|
237
|
+
self._access_checker = StaticAccessChecker(self)
|
|
238
|
+
|
|
239
|
+
@property
|
|
240
|
+
def read_accesses(self) -> Set[StaticAccessProto]:
|
|
241
|
+
"""Mutable set of read accesses to this signal."""
|
|
242
|
+
return self._read_accesses
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def write_accesses(self) -> Set[StaticAccessProto]:
|
|
246
|
+
"""Mutable set of write accesses to this signal."""
|
|
247
|
+
return self._write_accesses
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def last_read_access_thread(self) -> Optional[ThreadProto]:
|
|
251
|
+
"""Thread performing the last read access."""
|
|
252
|
+
return self._last_read_access_thread
|
|
253
|
+
|
|
254
|
+
@last_read_access_thread.setter
|
|
255
|
+
def last_read_access_thread(self, value: ThreadProto) -> None:
|
|
256
|
+
"""Thread performing the last read access."""
|
|
257
|
+
self._last_read_access_thread = value
|
|
258
|
+
|
|
259
|
+
@property
|
|
260
|
+
def last_write_access_thread(self) -> Optional[ThreadProto]:
|
|
261
|
+
"""Thread performing the last write access."""
|
|
262
|
+
return self._last_write_access_thread
|
|
263
|
+
|
|
264
|
+
@last_write_access_thread.setter
|
|
265
|
+
def last_write_access_thread(self, value: ThreadProto) -> None:
|
|
266
|
+
"""Thread performing the last write access."""
|
|
267
|
+
self._last_write_access_thread = value
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def access_checker(self) -> StaticAccessCheckerProto:
|
|
271
|
+
"""Static access checker."""
|
|
272
|
+
return self._access_checker
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class _EventSourceSignal(Generic[T_Signal], _BaseSignal):
|
|
276
|
+
"""Class for signals that can be used as the source for a event.
|
|
277
|
+
|
|
278
|
+
This is only the case for signals and slice signals, but not for scratch signals, due to their ephemeral nature.
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
def __init__(self, name: str) -> None:
|
|
282
|
+
super().__init__(name)
|
|
283
|
+
|
|
284
|
+
self._events: Dict[ParameterizedEvent, ModuleInstanceProto] = {}
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def events(self) -> Mapping[ParameterizedEvent, ModuleInstanceProto]:
|
|
288
|
+
"""Events for this signal."""
|
|
289
|
+
return self._events
|
|
290
|
+
|
|
291
|
+
def rising(self) -> T_Signal:
|
|
292
|
+
"""Create rising edge event."""
|
|
293
|
+
return self._create_edge_detector().get_connected_signal('RISING') # type: ignore[return-value]
|
|
294
|
+
|
|
295
|
+
def falling(self) -> T_Signal:
|
|
296
|
+
"""Create falling edge event."""
|
|
297
|
+
return self._create_edge_detector().get_connected_signal('FALLING') # type: ignore[return-value]
|
|
298
|
+
|
|
299
|
+
def delayed(self, cycles: Union[int, ParameterProto] = 1) -> T_Signal:
|
|
300
|
+
"""Create event for delayed signal."""
|
|
301
|
+
return self._create_delay(cycles).get_connected_signal('OUT') # type: ignore[return-value]
|
|
302
|
+
|
|
303
|
+
def synchronized(self) -> T_Signal:
|
|
304
|
+
"""Create event for synchronized signal."""
|
|
305
|
+
return self._create_synchronized().get_connected_signal('OUT') # type: ignore[return-value]
|
|
306
|
+
|
|
307
|
+
def _create_edge_detector(self) -> ModuleInstanceProto:
|
|
308
|
+
"""Creates the edge detector for the signal if it does not exist.
|
|
309
|
+
|
|
310
|
+
If the signal has more than one bit, this results in ValueError.
|
|
311
|
+
"""
|
|
312
|
+
if self.width != 1:
|
|
313
|
+
raise ValueError('Edge dectors can only be used in 1-bit signals!')
|
|
314
|
+
|
|
315
|
+
if (event := ParameterizedEvent('edge')) not in self.events:
|
|
316
|
+
instance_name = f'I_EVENT_EDGE_DETECTOR_{self.escaped_name}'
|
|
317
|
+
instance = self.engine.create_module_instance(module_name='nortl_edge_detector', instance_name=instance_name)
|
|
318
|
+
self._events[ParameterizedEvent('edge')] = instance
|
|
319
|
+
|
|
320
|
+
signal_rising = self.engine.define_local(f'EVENT_{self.escaped_name}_rising')
|
|
321
|
+
signal_falling = self.engine.define_local(f'EVENT_{self.escaped_name}_falling')
|
|
322
|
+
|
|
323
|
+
self.engine.connect_module_port(instance_name, 'SIGNAL', self) # type: ignore[arg-type]
|
|
324
|
+
self.engine.connect_module_port(instance_name, 'RISING', signal_rising)
|
|
325
|
+
self.engine.connect_module_port(instance_name, 'FALLING', signal_falling)
|
|
326
|
+
|
|
327
|
+
return instance
|
|
328
|
+
else:
|
|
329
|
+
return self.events[event]
|
|
330
|
+
|
|
331
|
+
def _create_delay(self, cycles: Union[int, ParameterProto] = 1) -> ModuleInstanceProto:
|
|
332
|
+
"""Create a delay for the signal if it does not exist."""
|
|
333
|
+
event = ParameterizedEvent('delay')
|
|
334
|
+
event.add_parameter('cycles', cycles)
|
|
335
|
+
|
|
336
|
+
if event not in self.events:
|
|
337
|
+
instance_name = f'I_DELAY_BY_{cycles}_{self.escaped_name}'
|
|
338
|
+
instance = self.engine.create_module_instance(module_name='nortl_delay', instance_name=instance_name)
|
|
339
|
+
self.engine.override_module_parameter(instance_name, 'DELAY_STEPS', cycles)
|
|
340
|
+
self.engine.override_module_parameter(instance_name, 'DATA_WIDTH', self.width)
|
|
341
|
+
|
|
342
|
+
self._events[event] = instance
|
|
343
|
+
|
|
344
|
+
delayed_signal = self.engine.define_local(f'EVENT_{self.escaped_name}_DELAY_BY_{cycles}', self.width)
|
|
345
|
+
|
|
346
|
+
self.engine.connect_module_port(instance_name, 'IN', self) # type: ignore[arg-type]
|
|
347
|
+
self.engine.connect_module_port(instance_name, 'OUT', delayed_signal)
|
|
348
|
+
|
|
349
|
+
return instance
|
|
350
|
+
else:
|
|
351
|
+
return self.events[event]
|
|
352
|
+
|
|
353
|
+
def _create_synchronized(self) -> ModuleInstanceProto:
|
|
354
|
+
"""Create sync module for this signal if it does not exist."""
|
|
355
|
+
if (event := ParameterizedEvent('sync')) not in self.events:
|
|
356
|
+
instance_name = f'I_SYNC_{self.escaped_name}'
|
|
357
|
+
instance = self.engine.create_module_instance(module_name='nortl_sync', instance_name=instance_name)
|
|
358
|
+
self.engine.override_module_parameter(instance_name, 'DATA_WIDTH', self.width)
|
|
359
|
+
|
|
360
|
+
self._events[event] = instance
|
|
361
|
+
|
|
362
|
+
delayed_signal = self.engine.define_local(f'EVENT_{self.escaped_name}_SYNCED')
|
|
363
|
+
|
|
364
|
+
self.engine.connect_module_port(instance_name, 'IN', self) # type: ignore[arg-type]
|
|
365
|
+
self.engine.connect_module_port(instance_name, 'OUT', delayed_signal)
|
|
366
|
+
|
|
367
|
+
return instance
|
|
368
|
+
else:
|
|
369
|
+
return self.events[event]
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class Signal(_AccessControlledSignal, _EventSourceSignal[SignalProto]):
|
|
373
|
+
"""Signal definition, representing a Verilog signal.
|
|
374
|
+
|
|
375
|
+
Attributes:
|
|
376
|
+
engine: Finite state machine associated with this signal.
|
|
377
|
+
type: The role of the signal (input, output, interface, internal, local).
|
|
378
|
+
name: Name of the signal.
|
|
379
|
+
width: Width in bits of the signal.
|
|
380
|
+
data_type: Data type of the signal. Defaults to 'logic'.
|
|
381
|
+
|
|
382
|
+
The signal types are defined as follows:
|
|
383
|
+
* input, output: Port of the module
|
|
384
|
+
* interface: Use a system verilog interface
|
|
385
|
+
* local: A local register used that is not passed to the outside
|
|
386
|
+
* internal: a signal created by internal data structures, not necessarily visible for the user
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
def __init__(
|
|
390
|
+
self,
|
|
391
|
+
engine: EngineProto,
|
|
392
|
+
type: SIGNAL_TYPES,
|
|
393
|
+
name: str,
|
|
394
|
+
width: Union[int, ParameterProto, Renderable] = 1,
|
|
395
|
+
data_type: str = 'logic',
|
|
396
|
+
is_synchronized: bool = False,
|
|
397
|
+
pulsing: bool = False,
|
|
398
|
+
assignment: Optional[Renderable] = None,
|
|
399
|
+
) -> None:
|
|
400
|
+
"""Initialize a signal.
|
|
401
|
+
|
|
402
|
+
Arguments:
|
|
403
|
+
engine: State machine container object.
|
|
404
|
+
name: Signal name.
|
|
405
|
+
type: Signal type.
|
|
406
|
+
width: Width in bits (default=1).
|
|
407
|
+
data_type: Data type of the signal (default='logic').
|
|
408
|
+
is_synchronized: Indicates, if the signal is synchronous to the local clock domain.
|
|
409
|
+
pulsing: Wether the signal resets automatically to 0 if not written in current state
|
|
410
|
+
assignment: Source expression for combinational assignment.
|
|
411
|
+
"""
|
|
412
|
+
if type != 'internal' and name.startswith('_'):
|
|
413
|
+
raise ValueError('Signal names must not start with an underscore!')
|
|
414
|
+
if assignment is not None:
|
|
415
|
+
if type == 'input':
|
|
416
|
+
raise ValueError('Input signals must not have a combinational assignment.')
|
|
417
|
+
if pulsing:
|
|
418
|
+
raise ValueError('Signals with a combinational assignment cannot be pulsing.')
|
|
419
|
+
|
|
420
|
+
super().__init__(name)
|
|
421
|
+
|
|
422
|
+
self._engine = engine
|
|
423
|
+
self._type = type
|
|
424
|
+
self._width = width
|
|
425
|
+
self._operand_width = width if isinstance(width, int) else None
|
|
426
|
+
self._data_type = data_type
|
|
427
|
+
self._is_synchronized = is_synchronized
|
|
428
|
+
self._pulsing = pulsing
|
|
429
|
+
self._assignment = assignment
|
|
430
|
+
|
|
431
|
+
@property
|
|
432
|
+
def engine(self) -> EngineProto:
|
|
433
|
+
"""NoRTL engine for this signal."""
|
|
434
|
+
return self._engine
|
|
435
|
+
|
|
436
|
+
@property
|
|
437
|
+
def type(self) -> SIGNAL_TYPES:
|
|
438
|
+
"""Signal type."""
|
|
439
|
+
return self._type
|
|
440
|
+
|
|
441
|
+
@property
|
|
442
|
+
def pulsing(self) -> bool:
|
|
443
|
+
"""Shows, if the signal is self-resetting to zero after one cycle."""
|
|
444
|
+
return self._pulsing
|
|
445
|
+
|
|
446
|
+
@property
|
|
447
|
+
def assignment(self) -> Optional[Renderable]:
|
|
448
|
+
"""Source expression for combination assignment."""
|
|
449
|
+
return self._assignment
|
|
450
|
+
|
|
451
|
+
@property
|
|
452
|
+
def escaped_name(self) -> str:
|
|
453
|
+
"""Name of signal with any special characters escaped.
|
|
454
|
+
|
|
455
|
+
For regular signals, this is equal to their name. For slice signals, it contains the position.
|
|
456
|
+
"""
|
|
457
|
+
return self.name
|
|
458
|
+
|
|
459
|
+
@property
|
|
460
|
+
def width(self) -> Union[int, ParameterProto, Renderable]:
|
|
461
|
+
"""Signal width in bits."""
|
|
462
|
+
return self._width
|
|
463
|
+
|
|
464
|
+
@property
|
|
465
|
+
def operand_width(self) -> Optional[int]:
|
|
466
|
+
"""Indicates the width when used as an operand.
|
|
467
|
+
|
|
468
|
+
A width of None means that the width is not fixed during execution of noRTL.
|
|
469
|
+
This is the case, if the signal width is based on a parameter.
|
|
470
|
+
"""
|
|
471
|
+
return self._operand_width
|
|
472
|
+
|
|
473
|
+
@property
|
|
474
|
+
def data_type(self) -> str:
|
|
475
|
+
"""Data type of the signal (e.g., 'logic', 'reg', etc.)."""
|
|
476
|
+
return self._data_type
|
|
477
|
+
|
|
478
|
+
def render(self, target: Optional[str] = None) -> str:
|
|
479
|
+
"""Render value to target language.
|
|
480
|
+
|
|
481
|
+
Arguments:
|
|
482
|
+
target: Target language.
|
|
483
|
+
"""
|
|
484
|
+
return self.name
|
|
485
|
+
|
|
486
|
+
def __getitem__(self, index: Union[int, IntSlice]) -> 'SignalSlice':
|
|
487
|
+
return SignalSlice(self, index)
|
|
488
|
+
|
|
489
|
+
def overlaps_with(self, other: AssignmentTarget) -> Union[bool, Literal['partial']]:
|
|
490
|
+
"""Check if signal overlaps with other signal or signal slice."""
|
|
491
|
+
return self.name == other.name
|
|
492
|
+
|
|
493
|
+
def read_access(self, ignore: Set[ACCESS_CHECKS] = set()) -> None:
|
|
494
|
+
"""Register read access from the current thread.
|
|
495
|
+
|
|
496
|
+
If the current thread differs from the last write acess, will invoke the access checker.
|
|
497
|
+
|
|
498
|
+
Raises:
|
|
499
|
+
ExclusiveReadError: If the signal was read from more than one thread.
|
|
500
|
+
NonIdenticalRWError: If the signals was written by one, and read from another thread.
|
|
501
|
+
"""
|
|
502
|
+
if self.assignment is not None:
|
|
503
|
+
# Trigger read access on the assignment expression
|
|
504
|
+
self.assignment.read_access(ignore=ignore)
|
|
505
|
+
else:
|
|
506
|
+
super().read_access(ignore=ignore)
|
|
507
|
+
|
|
508
|
+
def write_access(self, ignore: Set[ACCESS_CHECKS] = set()) -> None:
|
|
509
|
+
"""Register write access from the current thread.
|
|
510
|
+
|
|
511
|
+
If the current thread differs from the last write acess, will invoke the access checker.
|
|
512
|
+
|
|
513
|
+
Raises:
|
|
514
|
+
ExclusiveWriteError: If the signals was written by more than one thread.
|
|
515
|
+
NonIdenticalRWError: If the signals was written by one, and read from another thread.
|
|
516
|
+
WriteViolationError: If the signal is read-only.
|
|
517
|
+
"""
|
|
518
|
+
if self.type == 'input':
|
|
519
|
+
raise WriteViolationError(f'Input signal {self.name} is read-only.')
|
|
520
|
+
if self.assignment is not None:
|
|
521
|
+
raise WriteViolationError(f'Signal {self.name} is assigned to the expression {self.assignment}. It is read-only.')
|
|
522
|
+
else:
|
|
523
|
+
super().write_access(ignore=ignore)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
class _BaseSlice(_BaseSignal):
|
|
527
|
+
"""Intermediate class for signal slices."""
|
|
528
|
+
|
|
529
|
+
def __init__(self, signal: SignalProto, index: Union[int, IntSlice]) -> None:
|
|
530
|
+
super().__init__(signal.name)
|
|
531
|
+
|
|
532
|
+
self._base_signal = signal
|
|
533
|
+
self._index = index
|
|
534
|
+
|
|
535
|
+
if isinstance(signal.width, int):
|
|
536
|
+
if signal.width <= 1:
|
|
537
|
+
raise IndexError(f'Unable to slice signal with width {signal.width}!')
|
|
538
|
+
|
|
539
|
+
if isinstance(index, int):
|
|
540
|
+
if index < 0:
|
|
541
|
+
raise IndexError(f'Index {index} is out of bounds for signal {signal.name} with width {signal.width}')
|
|
542
|
+
if isinstance(signal.width, int) and index not in range(0, signal.width):
|
|
543
|
+
raise IndexError(f'Index {index} is out of bounds for signal {signal.name} with width {signal.width}')
|
|
544
|
+
|
|
545
|
+
self._width: int = 1
|
|
546
|
+
self._bitorder: Optional[BIT_ORDER] = None
|
|
547
|
+
else:
|
|
548
|
+
start, stop, bitorder = validate_slice(index)
|
|
549
|
+
self._width = stop - start + 1
|
|
550
|
+
self._bitorder = bitorder
|
|
551
|
+
|
|
552
|
+
@property
|
|
553
|
+
def base_signal(self) -> SignalProto:
|
|
554
|
+
"""Full-width signal of this slice."""
|
|
555
|
+
return self._base_signal
|
|
556
|
+
|
|
557
|
+
@property
|
|
558
|
+
def index(self) -> Union[int, IntSlice]:
|
|
559
|
+
"""Index of the signal."""
|
|
560
|
+
return self._index
|
|
561
|
+
|
|
562
|
+
# Properties forwarded to full-width signal
|
|
563
|
+
@property
|
|
564
|
+
def engine(self) -> EngineProto:
|
|
565
|
+
"""Finite state machine."""
|
|
566
|
+
return self.base_signal.engine
|
|
567
|
+
|
|
568
|
+
@property
|
|
569
|
+
def type(self) -> SIGNAL_TYPES:
|
|
570
|
+
"""Signal type."""
|
|
571
|
+
return self.base_signal.type
|
|
572
|
+
|
|
573
|
+
@property
|
|
574
|
+
def pulsing(self) -> bool:
|
|
575
|
+
"""Shows, if the signal is self-resetting to zero after one cycle."""
|
|
576
|
+
return self.base_signal.pulsing
|
|
577
|
+
|
|
578
|
+
@property
|
|
579
|
+
def escaped_name(self) -> str:
|
|
580
|
+
"""Name of signal with any special characters escaped.
|
|
581
|
+
|
|
582
|
+
For regular signals, this is equal to their name. For slice signals, it contains the position.
|
|
583
|
+
"""
|
|
584
|
+
if isinstance(self.index, int):
|
|
585
|
+
return f'{self.name}_{self.index}'
|
|
586
|
+
else:
|
|
587
|
+
return f'{self.name}_{self.index.start}to{self.index.stop}'
|
|
588
|
+
|
|
589
|
+
@property
|
|
590
|
+
def data_type(self) -> str:
|
|
591
|
+
"""Data type of the signal (e.g., 'logic', 'reg', etc.)."""
|
|
592
|
+
return self.base_signal.data_type
|
|
593
|
+
|
|
594
|
+
@property
|
|
595
|
+
def _is_synchronized(self) -> bool:
|
|
596
|
+
"""Indicates if the this signal is synchronized to the local clock domain."""
|
|
597
|
+
return self.base_signal._is_synchronized
|
|
598
|
+
|
|
599
|
+
# Additional properties
|
|
600
|
+
@property
|
|
601
|
+
def width(self) -> int:
|
|
602
|
+
"""Signal width in bits."""
|
|
603
|
+
return self._width
|
|
604
|
+
|
|
605
|
+
@property
|
|
606
|
+
def bitorder(self) -> Optional[BIT_ORDER]:
|
|
607
|
+
"""Bit order of signal."""
|
|
608
|
+
return self._bitorder
|
|
609
|
+
|
|
610
|
+
@property
|
|
611
|
+
def operand_width(self) -> int:
|
|
612
|
+
"""Indicates the width when used as an operand.
|
|
613
|
+
|
|
614
|
+
A width of None means that the width is not fixed during execution of noRTL.
|
|
615
|
+
This is the case, if the signal width is based on a parameter.
|
|
616
|
+
"""
|
|
617
|
+
return self.width
|
|
618
|
+
|
|
619
|
+
def overlaps_with(self, other: AssignmentTarget) -> Union[bool, Literal['partial']]:
|
|
620
|
+
"""Check if signal slice overlaps with other signal or signal slice."""
|
|
621
|
+
|
|
622
|
+
if self.name != other.name:
|
|
623
|
+
return False
|
|
624
|
+
|
|
625
|
+
# Unwrap content of modifier
|
|
626
|
+
if isinstance(other, BaseModifier):
|
|
627
|
+
other = other.content
|
|
628
|
+
|
|
629
|
+
if isinstance(other, _BaseSlice):
|
|
630
|
+
# Signal slice, check if it overlaps
|
|
631
|
+
if isinstance(self.index, int) and isinstance(other.index, int):
|
|
632
|
+
# Full overlap or none
|
|
633
|
+
return self.index == other.index
|
|
634
|
+
elif isinstance(self.index, slice) and isinstance(other.index, slice) and self.index == other.index:
|
|
635
|
+
# Full overlap, if the slices are exactly the same
|
|
636
|
+
return True
|
|
637
|
+
|
|
638
|
+
own_indexes = set(list_indexes(self.index))
|
|
639
|
+
other_indexes = set(list_indexes(other.index))
|
|
640
|
+
|
|
641
|
+
if own_indexes.isdisjoint(other_indexes):
|
|
642
|
+
return False
|
|
643
|
+
if own_indexes == other_indexes:
|
|
644
|
+
return True
|
|
645
|
+
|
|
646
|
+
# In all other cases (overlap of signal and slice, parametric width, partial overlap), treat overlap as partial
|
|
647
|
+
return 'partial'
|
|
648
|
+
|
|
649
|
+
def render(self, target: Optional[str] = None) -> str:
|
|
650
|
+
"""Render value to target language.
|
|
651
|
+
|
|
652
|
+
Arguments:
|
|
653
|
+
target: Target language.
|
|
654
|
+
"""
|
|
655
|
+
if isinstance(self.index, int):
|
|
656
|
+
return f'{self.name}[{self.index}]'
|
|
657
|
+
elif self.index.start == self.index.stop:
|
|
658
|
+
return f'{self.name}[{self.index.start}]'
|
|
659
|
+
else:
|
|
660
|
+
return f'{self.name}[{self.index.start}:{self.index.stop}]'
|
|
661
|
+
|
|
662
|
+
def __getitem__(self, index: Union[int, IntSlice]) -> Self:
|
|
663
|
+
if isinstance(self.index, int):
|
|
664
|
+
if index == 0:
|
|
665
|
+
return self # Allow indexing [0] of a single bit slice again
|
|
666
|
+
else:
|
|
667
|
+
raise IndexError(f'Unable to slice {index} from single-bit signal slice {self.escaped_name}: Only index 0 can be sliced.')
|
|
668
|
+
else:
|
|
669
|
+
# Assemble list of own indexes, with reference to the base signal
|
|
670
|
+
own_indexes = list_indexes(self.index)
|
|
671
|
+
own_start: int = own_indexes[0]
|
|
672
|
+
own_stop: int = own_indexes[-1]
|
|
673
|
+
|
|
674
|
+
# Check that the new index doesn't go out of range (this is not caught by pick_indexes)
|
|
675
|
+
if isinstance(index, int):
|
|
676
|
+
if index < 0 or index > (own_stop - own_start):
|
|
677
|
+
raise IndexError(f'Unable to slice {index} from signal slice {self.escaped_name}: Index is out of range.')
|
|
678
|
+
else:
|
|
679
|
+
start, stop, bitorder = validate_slice(index)
|
|
680
|
+
if start < 0 or stop > (own_stop - own_start):
|
|
681
|
+
raise IndexError(f'Unable to slice {index} from signal slice {self.escaped_name}: Index is out of range.')
|
|
682
|
+
if bitorder != self.bitorder:
|
|
683
|
+
raise IndexError(f'Unable to slice {index} from signal slice {self.escaped_name}: Reversing the bit order is not allowed')
|
|
684
|
+
|
|
685
|
+
# Pick the indexes for the nested slice, with reference to the base signal
|
|
686
|
+
new_indexes = pick_indexes(own_indexes, index)
|
|
687
|
+
|
|
688
|
+
if len(new_indexes) == 0:
|
|
689
|
+
raise IndexError(f'Unable to slice {index} from signal slice {self.escaped_name}: slice result in zero length signal.')
|
|
690
|
+
if len(new_indexes) == 1:
|
|
691
|
+
return type(self)(self.base_signal, new_indexes[0])
|
|
692
|
+
|
|
693
|
+
# As we don't support multiple indexes, we only need to find the new minimum and maximum indexes
|
|
694
|
+
if bitorder == 'H:L':
|
|
695
|
+
return type(self)(self.base_signal, slice(max(new_indexes), min(new_indexes)))
|
|
696
|
+
else:
|
|
697
|
+
return type(self)(self.base_signal, slice(min(new_indexes), max(new_indexes)))
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
class SignalSlice(_BaseSlice, _EventSourceSignal[SignalSliceProto]):
|
|
701
|
+
"""Slice of a signal."""
|
|
702
|
+
|
|
703
|
+
def __init__(self, signal: SignalProto, index: Union[int, IntSlice]) -> None:
|
|
704
|
+
# if not isinstance(signal.width, int):
|
|
705
|
+
# TODO: Do we need to pass metadata along?
|
|
706
|
+
# TODO: parameters or renderable widths cannot be validated
|
|
707
|
+
# raise NotImplementedError('Only signals with discrete width can be sliced!') # noqa: ERA001
|
|
708
|
+
super().__init__(signal, index)
|
|
709
|
+
|
|
710
|
+
# Forward access control to base signal
|
|
711
|
+
@property
|
|
712
|
+
def access_checker(self) -> StaticAccessCheckerProto:
|
|
713
|
+
"""Static access checker."""
|
|
714
|
+
return self.base_signal.access_checker
|
|
715
|
+
|
|
716
|
+
@property
|
|
717
|
+
def read_accesses(self) -> Set[StaticAccessProto]:
|
|
718
|
+
"""Mutable set of read accesses to this signal."""
|
|
719
|
+
return self.base_signal.read_accesses
|
|
720
|
+
|
|
721
|
+
@property
|
|
722
|
+
def write_accesses(self) -> Set[StaticAccessProto]:
|
|
723
|
+
"""Mutable set of write accesses to this signal."""
|
|
724
|
+
return self.base_signal.write_accesses
|
|
725
|
+
|
|
726
|
+
@property
|
|
727
|
+
def last_read_access_thread(self) -> Optional[ThreadProto]:
|
|
728
|
+
"""Thread performing the last read access."""
|
|
729
|
+
return self.base_signal.last_read_access_thread
|
|
730
|
+
|
|
731
|
+
@last_read_access_thread.setter
|
|
732
|
+
def last_read_access_thread(self, value: ThreadProto) -> None:
|
|
733
|
+
"""Thread performing the last read access."""
|
|
734
|
+
self.base_signal.last_read_access_thread = value
|
|
735
|
+
|
|
736
|
+
@property
|
|
737
|
+
def last_write_access_thread(self) -> Optional[ThreadProto]:
|
|
738
|
+
"""Thread performing the last write access."""
|
|
739
|
+
return self.base_signal.last_write_access_thread
|
|
740
|
+
|
|
741
|
+
@last_write_access_thread.setter
|
|
742
|
+
def last_write_access_thread(self, value: ThreadProto) -> None:
|
|
743
|
+
"""Thread performing the last write access."""
|
|
744
|
+
self.base_signal.last_write_access_thread = value
|
|
745
|
+
|
|
746
|
+
def as_scratch_signal(self) -> 'ScratchSignal':
|
|
747
|
+
"""Turn SignalSlice into ScratchSignal, owned by the current thread."""
|
|
748
|
+
return ScratchSignal(self.base_signal, self.index)
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
class ScratchSignal(_BaseSlice, _AccessControlledSignal):
|
|
752
|
+
"""A scratch signal is a special kind of signal slice, that is only valid for a limited time."""
|
|
753
|
+
|
|
754
|
+
def __init__(self, signal: SignalProto, index: Union[int, IntSlice]) -> None:
|
|
755
|
+
super().__init__(signal, index)
|
|
756
|
+
|
|
757
|
+
self._owner = signal.engine.current_thread
|
|
758
|
+
|
|
759
|
+
# Access control for scratch signals
|
|
760
|
+
self._released: bool = False
|
|
761
|
+
self._context_ctr: int = 0
|
|
762
|
+
self._context_ctr_active: bool = True
|
|
763
|
+
|
|
764
|
+
@property
|
|
765
|
+
def owner(self) -> ThreadProto:
|
|
766
|
+
"""Owner of this scratch signal."""
|
|
767
|
+
return self._owner
|
|
768
|
+
|
|
769
|
+
def __enter__(self) -> Self:
|
|
770
|
+
return self
|
|
771
|
+
|
|
772
|
+
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]) -> None:
|
|
773
|
+
self.release()
|
|
774
|
+
|
|
775
|
+
def enter_context(self) -> None:
|
|
776
|
+
"""Context counting method.
|
|
777
|
+
|
|
778
|
+
The claim/release logic relies on the concept, that a scratch variable is to be released in the context where it has been created.
|
|
779
|
+
A context is represented by a Condition or a Loop construct. Both are created with context managers and may be nested.
|
|
780
|
+
The idea of this function is to count the 'depth' of context nests that we are currently working with.
|
|
781
|
+
|
|
782
|
+
In this way, we can detect, if the user tries to release the signal in a different context than creation. Once the release function is called,
|
|
783
|
+
we stop context counting since the scratch signal is now inactive and the claim/release-control is now realized based on the currently running threads.
|
|
784
|
+
|
|
785
|
+
This concept assumes, that each context manager triggers the `enter_context` function during enter and the `exit_context` function during exit.
|
|
786
|
+
The `exit_context` function automatically releases the signal, if we leave the claiming context.
|
|
787
|
+
"""
|
|
788
|
+
if self._context_ctr_active:
|
|
789
|
+
self._context_ctr += 1
|
|
790
|
+
|
|
791
|
+
def exit_context(self) -> None:
|
|
792
|
+
"""Context counting method. Explanation in exit_context function."""
|
|
793
|
+
if self._context_ctr_active:
|
|
794
|
+
self._context_ctr -= 1
|
|
795
|
+
|
|
796
|
+
if self._context_ctr == -1 and self.owner == self.engine.current_thread:
|
|
797
|
+
self._context_ctr = 0
|
|
798
|
+
self.release()
|
|
799
|
+
|
|
800
|
+
# Access control
|
|
801
|
+
def write_access(self, ignore: Set[ACCESS_CHECKS] = set()) -> None:
|
|
802
|
+
"""Register write access from the current thread.
|
|
803
|
+
|
|
804
|
+
If the current thread differs from the last write acess, will invoke the access checker.
|
|
805
|
+
|
|
806
|
+
Raises:
|
|
807
|
+
ExclusiveWriteError: If the signals was written by more than one thread.
|
|
808
|
+
NonIdenticalRWError: If the signals was written by one, and read from another thread.
|
|
809
|
+
AccessAfterReleaseError: If the scratch signal was released.
|
|
810
|
+
"""
|
|
811
|
+
if self.released:
|
|
812
|
+
raise AccessAfterReleaseError('Tried to write to a signal that has been released previously!')
|
|
813
|
+
super().write_access(ignore=ignore)
|
|
814
|
+
|
|
815
|
+
def read_access(self, ignore: Set[ACCESS_CHECKS] = set()) -> None:
|
|
816
|
+
"""Register read access from the current thread.
|
|
817
|
+
|
|
818
|
+
If the current thread differs from the last write acess, will invoke the access checker.
|
|
819
|
+
|
|
820
|
+
Raises:
|
|
821
|
+
ExclusiveReadError: If the signal was read from more than one thread.
|
|
822
|
+
NonIdenticalRWError: If the signals was written by one, and read from another thread.
|
|
823
|
+
AccessAfterReleaseError: If the scratch signal was released.
|
|
824
|
+
"""
|
|
825
|
+
if self.released:
|
|
826
|
+
raise AccessAfterReleaseError('Tried to read from a signal that has been released previously!')
|
|
827
|
+
super().read_access(ignore=ignore)
|
|
828
|
+
|
|
829
|
+
@property
|
|
830
|
+
def released(self) -> bool:
|
|
831
|
+
"""Findout, if a signal has been release yet.
|
|
832
|
+
|
|
833
|
+
The claim/release control works based on two principles:
|
|
834
|
+
|
|
835
|
+
1. A scratch signal may only be claimed and released in a single code block. Example:
|
|
836
|
+
```python
|
|
837
|
+
f = CoreEngine("my_engine")
|
|
838
|
+
|
|
839
|
+
with Condition(f, some_condition):
|
|
840
|
+
s = # new scratch signal
|
|
841
|
+
|
|
842
|
+
with ForLoop(...):
|
|
843
|
+
# s may not be released here
|
|
844
|
+
|
|
845
|
+
s.release() # s can be released here, since it is the same context
|
|
846
|
+
|
|
847
|
+
# After the context has ended, s is automatically released.
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
2. A scratch signal will appear as non-released in parallel threads and will be released once the owner thread ends. Additional access control applies.
|
|
851
|
+
```python
|
|
852
|
+
f = CoreEngine("my_engine")
|
|
853
|
+
|
|
854
|
+
with Fork(f, "my_fork") as f1:
|
|
855
|
+
s = # new scratch_signals
|
|
856
|
+
#...
|
|
857
|
+
s.release()
|
|
858
|
+
with Fork(f, "my_second_fork") as f2:
|
|
859
|
+
assert s.released == False # Parallel running thread the scratch pad location!
|
|
860
|
+
|
|
861
|
+
f1.wait_for_finish()
|
|
862
|
+
|
|
863
|
+
# s is released automatically once the thread has finished.
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
"""
|
|
867
|
+
if self.owner != self.base_signal.engine.current_thread:
|
|
868
|
+
return not self.owner.running
|
|
869
|
+
return self._released
|
|
870
|
+
|
|
871
|
+
def release(self, force: bool = False) -> None:
|
|
872
|
+
if self.owner != self.base_signal.engine.current_thread and not force:
|
|
873
|
+
raise ValueError('Scratch register may only be released in owning thread!')
|
|
874
|
+
if self._context_ctr != 0 and not force:
|
|
875
|
+
raise ValueError('Scratch signals need to be released in the context where they were created!')
|
|
876
|
+
|
|
877
|
+
self._context_ctr_active = False
|
|
878
|
+
self._released = True
|