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/parameter.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""HDL Parameters."""
|
|
2
|
+
|
|
3
|
+
from typing import Final, Optional, Set
|
|
4
|
+
|
|
5
|
+
from .operations import OperationTrait
|
|
6
|
+
from .protocols import ACCESS_CHECKS, EngineProto
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
'Parameter',
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Parameter(OperationTrait):
|
|
14
|
+
"""Parameter definition, representing a Verilog parameter.
|
|
15
|
+
|
|
16
|
+
The parameters are to be considered of data type int.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
engine: Finite state machine associated with this Parameter.
|
|
20
|
+
name: Name of the Parameter.
|
|
21
|
+
default_value: Default value of the Parameter.
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
is_primitive: Final = True
|
|
26
|
+
|
|
27
|
+
def __init__(self, engine: EngineProto, name: str, default_value: int, width: Optional[int] = None) -> None:
|
|
28
|
+
"""Initialize a Parameter object.
|
|
29
|
+
|
|
30
|
+
Arguments:
|
|
31
|
+
engine: State machine container object.
|
|
32
|
+
name: Parameter name.
|
|
33
|
+
default_value: Default value of the parameter
|
|
34
|
+
width: Width of the parameter.
|
|
35
|
+
"""
|
|
36
|
+
if name.startswith('_'):
|
|
37
|
+
raise ValueError('Parameter names must not start with an underscore!')
|
|
38
|
+
|
|
39
|
+
self._engine = engine
|
|
40
|
+
self._name = name
|
|
41
|
+
self._default_value = default_value
|
|
42
|
+
self._width = width
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def engine(self) -> EngineProto:
|
|
46
|
+
"""Finite state machine."""
|
|
47
|
+
return self._engine
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def name(self) -> str:
|
|
51
|
+
"""Parameter name."""
|
|
52
|
+
return self._name
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def default_value(self) -> int:
|
|
56
|
+
"""Default value."""
|
|
57
|
+
return self._default_value
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def width(self) -> Optional[int]:
|
|
61
|
+
"""Indicates the width of the parameter in bits.
|
|
62
|
+
|
|
63
|
+
Parameters with a fixed width will be rendered as `parameter [<width>-1:0] <name>`.
|
|
64
|
+
"""
|
|
65
|
+
return self._width
|
|
66
|
+
|
|
67
|
+
# Implement OperationTrait
|
|
68
|
+
@property
|
|
69
|
+
def operand_width(self) -> Optional[int]:
|
|
70
|
+
"""Indicates the width when used as an operand.
|
|
71
|
+
|
|
72
|
+
A width of None means that the width is not fixed during execution of noRTL.
|
|
73
|
+
"""
|
|
74
|
+
return self.width
|
|
75
|
+
|
|
76
|
+
def read_access(self, ignore: Set[ACCESS_CHECKS] = set()) -> None:
|
|
77
|
+
"""Register read access from the current thread, state and construct depth.
|
|
78
|
+
|
|
79
|
+
Does not invoke any checks for this object.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def render(self, target: Optional[str] = None) -> str:
|
|
83
|
+
"""Render value to target language.
|
|
84
|
+
|
|
85
|
+
Arguments:
|
|
86
|
+
target: Target language.
|
|
87
|
+
"""
|
|
88
|
+
return self.name
|
nortl/core/process.py
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import List, Optional, Sequence, Set
|
|
3
|
+
|
|
4
|
+
from typing_extensions import Self
|
|
5
|
+
|
|
6
|
+
from nortl.core.exceptions import OwnershipError, UnfinishedForwardDeclarationError
|
|
7
|
+
|
|
8
|
+
from .common import NamedEntity
|
|
9
|
+
from .modifiers import Volatile
|
|
10
|
+
from .operations import Const, Var
|
|
11
|
+
from .protocols import EngineProto, Renderable, SignalProto, StateProto, WorkerProto
|
|
12
|
+
from .state import State
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Worker(NamedEntity):
|
|
16
|
+
"""Worker for noRTL engine.
|
|
17
|
+
|
|
18
|
+
Each worker represents a state machine, that runs independently from the others.
|
|
19
|
+
The workers store their states. Creating state transitions between different workers is not possible.
|
|
20
|
+
|
|
21
|
+
The noRTL engine contains one main worker by default. Additional workers can be created manually.
|
|
22
|
+
The main worker is responsible for managing the reset state for all signals; the other workers do not contain any assignments in the reset state.
|
|
23
|
+
|
|
24
|
+
Each worker must have one or more threads.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, engine: EngineProto, name: str, reset_state_name: str = 'IDLE') -> None:
|
|
28
|
+
"""Initialize a new Worker.
|
|
29
|
+
|
|
30
|
+
Arguments:
|
|
31
|
+
engine: noRTL engine.
|
|
32
|
+
name: Name of the worker.
|
|
33
|
+
reset_state_name: Name of the reset state for this worker.
|
|
34
|
+
|
|
35
|
+
!!! warning
|
|
36
|
+
|
|
37
|
+
Workers are not meant to be instantiated manually. Use the [`CoreEngine.create_worker()`][nortl.core.engine.CoreEngine.create_worker] method instead.
|
|
38
|
+
"""
|
|
39
|
+
super().__init__(name)
|
|
40
|
+
|
|
41
|
+
self._engine = engine
|
|
42
|
+
self._is_main_worker = name == engine.MAIN_WORKER_NAME
|
|
43
|
+
|
|
44
|
+
# State tracking
|
|
45
|
+
self._states: List[State] = []
|
|
46
|
+
self._state_names: Set[str] = set()
|
|
47
|
+
|
|
48
|
+
# Thread tracking
|
|
49
|
+
self._threads: List['Thread'] = [] # Threads mapped to this worker
|
|
50
|
+
|
|
51
|
+
# Create reset state and set to current state
|
|
52
|
+
self._reset_state = self.create_state(reset_state_name, allow_assignments=self.is_main_worker)
|
|
53
|
+
self._current_state = self.reset_state
|
|
54
|
+
self._next_state: Optional[State] = None
|
|
55
|
+
|
|
56
|
+
# Internal control signals
|
|
57
|
+
# The main worker does not use these signals
|
|
58
|
+
self._reset_signal: Optional[Volatile[SignalProto]] = None
|
|
59
|
+
self._start_signal: Optional[Volatile[SignalProto]] = None
|
|
60
|
+
self._select_signal: Optional[Volatile[SignalProto]] = None
|
|
61
|
+
self._idle_signal: Optional[Volatile[SignalProto]] = None
|
|
62
|
+
self._select_signal_width: Optional[Var] = None
|
|
63
|
+
|
|
64
|
+
# Synchronous reset, inactive by default
|
|
65
|
+
self._sync_reset: Optional[Renderable] = None
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def engine(self) -> EngineProto:
|
|
69
|
+
"""NoRTL engine for this worker."""
|
|
70
|
+
return self._engine
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def is_main_worker(self) -> bool:
|
|
74
|
+
"""If this worker is the main worker of the noRTL engine."""
|
|
75
|
+
return self._is_main_worker
|
|
76
|
+
|
|
77
|
+
def create_scoped_name(self, name: str) -> str:
|
|
78
|
+
"""Create scoped name for signals or other things.
|
|
79
|
+
|
|
80
|
+
If this worker is not the main worker, the name is prefixed with the name of the worker.
|
|
81
|
+
"""
|
|
82
|
+
# FIXME decide if worker prefix is omitted for main worker
|
|
83
|
+
if not self.is_main_worker and not name.startswith(self.name):
|
|
84
|
+
name = f'{self.name}_{name}'
|
|
85
|
+
return name
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def sync_reset(self) -> Renderable:
|
|
89
|
+
"""Synchronous reset for worker."""
|
|
90
|
+
if self._sync_reset is None:
|
|
91
|
+
return Const(0)
|
|
92
|
+
return self._sync_reset
|
|
93
|
+
|
|
94
|
+
@sync_reset.setter
|
|
95
|
+
def sync_reset(self, value: Renderable) -> None:
|
|
96
|
+
"""Synchronous reset for worker."""
|
|
97
|
+
if self._sync_reset is not None:
|
|
98
|
+
raise RuntimeError(f'Synchronous reset for worker {self.name} was already set to {self.sync_reset}.')
|
|
99
|
+
self._sync_reset = value
|
|
100
|
+
|
|
101
|
+
# Control Signals for Threads
|
|
102
|
+
# FIXME technically, these signals are only required for Fork/Join management. They serve no purpose for raw Worker + Thread
|
|
103
|
+
@property
|
|
104
|
+
def reset(self) -> SignalProto:
|
|
105
|
+
"""Control signal to reset worker.
|
|
106
|
+
|
|
107
|
+
If the worker control signal is used, it is automatically added to the synchronous reset.
|
|
108
|
+
"""
|
|
109
|
+
if self.is_main_worker:
|
|
110
|
+
raise RuntimeError('Main worker does not use reset signal.')
|
|
111
|
+
elif self._reset_signal is None:
|
|
112
|
+
self._initialize_control_signals()
|
|
113
|
+
return self._reset_signal # type: ignore[return-value]
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def start(self) -> SignalProto:
|
|
117
|
+
"""Control signal to start worker."""
|
|
118
|
+
if self.is_main_worker:
|
|
119
|
+
raise RuntimeError('Main worker does not use start signal.')
|
|
120
|
+
elif self._start_signal is None:
|
|
121
|
+
self._initialize_control_signals()
|
|
122
|
+
return self._start_signal # type: ignore[return-value]
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def select(self) -> SignalProto:
|
|
126
|
+
"""Control signal that selects a thread."""
|
|
127
|
+
if self.is_main_worker:
|
|
128
|
+
raise RuntimeError('Main worker does not use select signal.')
|
|
129
|
+
elif self._select_signal is None:
|
|
130
|
+
self._initialize_control_signals()
|
|
131
|
+
return self._select_signal # type: ignore[return-value]
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def idle(self) -> SignalProto:
|
|
135
|
+
"""Status signal that indicates if the worker is in it's idle state."""
|
|
136
|
+
if self.is_main_worker:
|
|
137
|
+
raise RuntimeError('Main worker does not use idle signal.')
|
|
138
|
+
elif self._idle_signal is None:
|
|
139
|
+
self._initialize_control_signals()
|
|
140
|
+
return self._idle_signal # type: ignore[return-value]
|
|
141
|
+
|
|
142
|
+
def _initialize_control_signals(self) -> None:
|
|
143
|
+
"""Initializes built-in signals for the worker."""
|
|
144
|
+
|
|
145
|
+
# Local Signals
|
|
146
|
+
self._reset_signal = Volatile(
|
|
147
|
+
self.engine.define_local(self.create_scoped_name('reset'), width=1, reset_value=0, pulsing=True), 'identical_rw', 'exclusive_write'
|
|
148
|
+
)
|
|
149
|
+
self._start_signal = Volatile(
|
|
150
|
+
self.engine.define_local(self.create_scoped_name('start'), width=1, reset_value=0, pulsing=True), 'identical_rw', 'exclusive_write'
|
|
151
|
+
)
|
|
152
|
+
self._select_signal_width = Var(self._get_select_width())
|
|
153
|
+
self._select_signal = Volatile(
|
|
154
|
+
self.engine.define_local(self.create_scoped_name('select'), width=self._select_signal_width, reset_value=0),
|
|
155
|
+
'identical_rw',
|
|
156
|
+
'exclusive_write',
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# the following signal has to be set to zero once a thread is started:
|
|
160
|
+
# FIXME: Set signal to zero and to one after first state
|
|
161
|
+
self._idle_signal = Volatile(self.engine.define_local(self.create_scoped_name('idle'), reset_value=1), 'identical_rw', 'exclusive_write')
|
|
162
|
+
|
|
163
|
+
# Tie synchrounous reset to the new reset signal
|
|
164
|
+
self.sync_reset = self.reset
|
|
165
|
+
|
|
166
|
+
def _get_select_width(self) -> int:
|
|
167
|
+
"""Calculate necessary width for thread select signal."""
|
|
168
|
+
return math.ceil(math.log2(len(self.threads)) + 1)
|
|
169
|
+
|
|
170
|
+
def _resize_select_signal(self) -> None:
|
|
171
|
+
"""Updates the internal signal width of the select variable."""
|
|
172
|
+
if self._select_signal_width is not None:
|
|
173
|
+
self._select_signal_width.update(self._get_select_width())
|
|
174
|
+
|
|
175
|
+
# State management
|
|
176
|
+
@property
|
|
177
|
+
def states(self) -> Sequence[State]:
|
|
178
|
+
"""List of states for this worker."""
|
|
179
|
+
return self._states
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def state_names(self) -> Set[str]:
|
|
183
|
+
"""Set of the names of all states for this worker."""
|
|
184
|
+
return self._state_names
|
|
185
|
+
|
|
186
|
+
def create_state(self, name: Optional[str] = None, allow_assignments: bool = True) -> State:
|
|
187
|
+
"""Create a state.
|
|
188
|
+
|
|
189
|
+
Arguments:
|
|
190
|
+
name: Optional state name. If no name is provided, it defaults to '<worker_name>_STATE_<id>', where 'id' is the current number of states
|
|
191
|
+
for the worker and 'worker_name' is the name of this worker.
|
|
192
|
+
|
|
193
|
+
If this worker is not the main worker, the name of the state must be prefixed with the name of the current worker.
|
|
194
|
+
The prefix is automatically added, if missing.
|
|
195
|
+
allow_assignments: If the state allows assignments. This is used for internal purposes.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
The created state.
|
|
199
|
+
"""
|
|
200
|
+
# Generate default state name
|
|
201
|
+
if name is None:
|
|
202
|
+
name = f'STATE_{len(self.states)}'
|
|
203
|
+
|
|
204
|
+
if len(self.states) > 0 and len(self.threads) == 0:
|
|
205
|
+
raise RuntimeError('Worker has no thread, unable to create new states.')
|
|
206
|
+
|
|
207
|
+
# State will validate the name
|
|
208
|
+
state = State(self, name, allow_assignments=allow_assignments)
|
|
209
|
+
self._states.append(state)
|
|
210
|
+
return state
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def current_state(self) -> State:
|
|
214
|
+
"""Current state.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
The current state.
|
|
218
|
+
"""
|
|
219
|
+
return self._current_state
|
|
220
|
+
|
|
221
|
+
@current_state.setter
|
|
222
|
+
def current_state(self, state: StateProto) -> None:
|
|
223
|
+
"""Current state.
|
|
224
|
+
|
|
225
|
+
Arguments:
|
|
226
|
+
state: The new current state.
|
|
227
|
+
|
|
228
|
+
Raises:
|
|
229
|
+
UnfinishedForwardDeclarationError: If the next state has been forward-declared and is different from the new current state.
|
|
230
|
+
"""
|
|
231
|
+
if state.engine is not self.engine:
|
|
232
|
+
raise OwnershipError('State does not belong to this engine.')
|
|
233
|
+
if state not in self.states:
|
|
234
|
+
raise OwnershipError('State does not belong to this worker.')
|
|
235
|
+
if self._next_state is not None and state is not self._next_state:
|
|
236
|
+
raise UnfinishedForwardDeclarationError(
|
|
237
|
+
'You have forward-declared the next state (by using next_state), but are now trying to set another state as the current one. '
|
|
238
|
+
'This may result in dead-end states and is therefore forbidden. Please switch to the next_state and modify it first.'
|
|
239
|
+
)
|
|
240
|
+
if state is not self.current_state:
|
|
241
|
+
self._next_state = None # Clear next state
|
|
242
|
+
self._current_state = state # type: ignore[assignment]
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def next_state(self) -> State:
|
|
246
|
+
"""Forward-declared next state.
|
|
247
|
+
|
|
248
|
+
This simplifies the creation of new states for non-branching sections of the state graph (e.g. via sync() or wait_for()).
|
|
249
|
+
When you use next_state you must set it as current_state, before you can set any other state.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
The forward-declared next state.
|
|
253
|
+
"""
|
|
254
|
+
if self._next_state is None:
|
|
255
|
+
self._next_state = self.create_state()
|
|
256
|
+
|
|
257
|
+
return self._next_state
|
|
258
|
+
|
|
259
|
+
@property
|
|
260
|
+
def reset_state(self) -> State:
|
|
261
|
+
"""The reset state of the engine.
|
|
262
|
+
|
|
263
|
+
This is the initial state from which the engine will start.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
The reset state.
|
|
267
|
+
"""
|
|
268
|
+
return self._reset_state
|
|
269
|
+
|
|
270
|
+
def leave_foreground(self) -> None:
|
|
271
|
+
"""This method must be called when the current worker of the engine is changed.
|
|
272
|
+
|
|
273
|
+
Checks if the any forward-declared next-state pending.
|
|
274
|
+
"""
|
|
275
|
+
if self._next_state is not None:
|
|
276
|
+
raise UnfinishedForwardDeclarationError(
|
|
277
|
+
'You have forward-declared the next state for the current worker (by using next_state), but are now trying to set another worker as the current one. '
|
|
278
|
+
'This may result in dead-end states and is therefore forbidden. Please finish the current worker first, by switching to the next_state and modifying it.'
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Thread Management
|
|
282
|
+
@property
|
|
283
|
+
def threads(self) -> Sequence['Thread']:
|
|
284
|
+
"""Stack of worker threads."""
|
|
285
|
+
return self._threads
|
|
286
|
+
|
|
287
|
+
def create_thread(self, name: Optional[str] = None) -> 'Thread':
|
|
288
|
+
"""Create a new thread.
|
|
289
|
+
|
|
290
|
+
Arguments:
|
|
291
|
+
name: Name of the thread.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
The created thread.
|
|
295
|
+
"""
|
|
296
|
+
# Generate default name
|
|
297
|
+
if name is None:
|
|
298
|
+
if len(self.threads) == 0:
|
|
299
|
+
name = self.engine.MAIN_THREAD_NAME
|
|
300
|
+
else:
|
|
301
|
+
name = f'thread_{len(self.threads)}'
|
|
302
|
+
|
|
303
|
+
thread = Thread(self, name)
|
|
304
|
+
self._threads.append(thread)
|
|
305
|
+
|
|
306
|
+
if not self.is_main_worker:
|
|
307
|
+
self._resize_select_signal() # Adjust width of select signal
|
|
308
|
+
return thread
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def current_thread(self) -> 'Thread':
|
|
312
|
+
"""Current thread for this worker."""
|
|
313
|
+
if len(self.threads) == 0:
|
|
314
|
+
raise RuntimeError('Worker has no threads.')
|
|
315
|
+
return self.threads[-1]
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def working(self) -> bool:
|
|
319
|
+
"""If the worker is working."""
|
|
320
|
+
return any([t.running for t in self.threads])
|
|
321
|
+
|
|
322
|
+
# Misc.
|
|
323
|
+
# FIXME these helper methods are only used by Fork/Join
|
|
324
|
+
def raise_reset(self) -> None:
|
|
325
|
+
self.engine.set(self.reset, 1)
|
|
326
|
+
|
|
327
|
+
def clear_reset(self) -> None:
|
|
328
|
+
self.engine.set(self.reset, 0)
|
|
329
|
+
self.engine.set(self.idle, 1)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class Thread(NamedEntity):
|
|
333
|
+
"""Thread for noRTL engine.
|
|
334
|
+
|
|
335
|
+
Threads are used to control signal ownership.
|
|
336
|
+
They prevent simultaneous access to signals from two workers, that would cause wrong behavior.
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
def __init__(self, worker: WorkerProto, name: str) -> None:
|
|
340
|
+
"""Initialize a new thread.
|
|
341
|
+
|
|
342
|
+
Arguments:
|
|
343
|
+
worker: Worker for this thread.
|
|
344
|
+
name: Name of the thread.
|
|
345
|
+
|
|
346
|
+
!!! warning
|
|
347
|
+
|
|
348
|
+
Threads are not meant to be instantiated manually. Use the [`Worker.create_thread()`][nortl.core.process.Worker.create_thread] method instead.
|
|
349
|
+
"""
|
|
350
|
+
super().__init__(name)
|
|
351
|
+
|
|
352
|
+
self._worker: WorkerProto = worker
|
|
353
|
+
self._is_main_thread = worker.is_main_worker
|
|
354
|
+
self._active: bool = self.is_main_thread # Main thread is always active
|
|
355
|
+
|
|
356
|
+
self._spawned_threads: List[Self] = []
|
|
357
|
+
|
|
358
|
+
# Link back the parent thread
|
|
359
|
+
self.parent_thread: Optional[Self] = None
|
|
360
|
+
if name != worker.engine.MAIN_THREAD_NAME:
|
|
361
|
+
self.parent_thread = self.worker.engine.current_thread # type: ignore[assignment]
|
|
362
|
+
|
|
363
|
+
@property
|
|
364
|
+
def engine(self) -> EngineProto:
|
|
365
|
+
"""NoRTL engine for this thread."""
|
|
366
|
+
return self.worker.engine
|
|
367
|
+
|
|
368
|
+
@property
|
|
369
|
+
def worker(self) -> WorkerProto:
|
|
370
|
+
"""Engine worker."""
|
|
371
|
+
return self._worker
|
|
372
|
+
|
|
373
|
+
@property
|
|
374
|
+
def is_main_thread(self) -> bool:
|
|
375
|
+
"""If this thread is the main thread of the noRTL engine."""
|
|
376
|
+
return self.worker.is_main_worker
|
|
377
|
+
|
|
378
|
+
@property
|
|
379
|
+
def active(self) -> bool:
|
|
380
|
+
"""If this thread is currently active."""
|
|
381
|
+
# FIXME decide if activity can be tracked automatically
|
|
382
|
+
return self._active
|
|
383
|
+
|
|
384
|
+
@active.setter
|
|
385
|
+
def active(self, value: bool) -> None:
|
|
386
|
+
"""If this thread is currently active."""
|
|
387
|
+
if self.is_main_thread:
|
|
388
|
+
raise RuntimeError('Main thread cannot be deactivated.')
|
|
389
|
+
self._active = value
|
|
390
|
+
|
|
391
|
+
def join(self) -> None:
|
|
392
|
+
"""Join a thread.
|
|
393
|
+
|
|
394
|
+
This waits for the thread to be finished.
|
|
395
|
+
"""
|
|
396
|
+
self.engine.wait_for(self.finished)
|
|
397
|
+
self.active = False
|
|
398
|
+
self.engine.scratch_manager.force_release_signals_by_thread(self)
|
|
399
|
+
self.engine.signal_manager.free_accesses_from_thread(self)
|
|
400
|
+
|
|
401
|
+
@property
|
|
402
|
+
def finished(self) -> Renderable:
|
|
403
|
+
return self.worker.idle
|
|
404
|
+
|
|
405
|
+
@property
|
|
406
|
+
def running(self) -> bool:
|
|
407
|
+
if self.parent_thread == self.engine.current_thread:
|
|
408
|
+
return self.active
|
|
409
|
+
|
|
410
|
+
if self.active:
|
|
411
|
+
return True
|
|
412
|
+
|
|
413
|
+
global_call_stack = self.engine.current_thread.call_stack
|
|
414
|
+
|
|
415
|
+
for t in self.call_stack:
|
|
416
|
+
if t not in global_call_stack:
|
|
417
|
+
if t.active:
|
|
418
|
+
return True
|
|
419
|
+
|
|
420
|
+
return False
|
|
421
|
+
|
|
422
|
+
def cancel(self) -> None:
|
|
423
|
+
"""This method sends a synchronous reset to the Thread's worker and recursively to all threads, that are spawned from this thread."""
|
|
424
|
+
for subthread in self._spawned_threads:
|
|
425
|
+
subthread.cancel()
|
|
426
|
+
|
|
427
|
+
self.worker.raise_reset()
|
|
428
|
+
self.engine.sync()
|
|
429
|
+
self.worker.clear_reset()
|
|
430
|
+
self.engine.sync()
|
|
431
|
+
|
|
432
|
+
self.active = False
|
|
433
|
+
self.engine.scratch_manager.force_release_signals_by_thread(self)
|
|
434
|
+
self.engine.signal_manager.free_accesses_from_thread(self)
|
|
435
|
+
|
|
436
|
+
def finish(self) -> None:
|
|
437
|
+
"""Finishes a Thread."""
|
|
438
|
+
self.engine.jump_if(Const(1), self.worker.reset_state)
|
|
439
|
+
self.engine.set(self.worker.idle, 1)
|
|
440
|
+
|
|
441
|
+
@property
|
|
442
|
+
def call_stack(self) -> List[Self]:
|
|
443
|
+
ret = []
|
|
444
|
+
|
|
445
|
+
thread = self
|
|
446
|
+
|
|
447
|
+
while thread.parent_thread is not None:
|
|
448
|
+
ret.append(thread.parent_thread)
|
|
449
|
+
thread = thread.parent_thread
|
|
450
|
+
|
|
451
|
+
return ret
|